snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

8
server/.dockerignore Normal file
View File

@@ -0,0 +1,8 @@
webconsole/dist
webconsole/node_modules
.idea
.dev
build
codegen
var
webapp

19
server/.editorconfig Normal file
View File

@@ -0,0 +1,19 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.go]
indent_style = space
[*.cue]
indent_style = space
indent_size = 2
# Tab indentation (no size specified)
[Makefile]
indent_style = tab

2
server/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
**/*gen.go linguist-generated=true
**/*gen_test.go linguist-generated=true

21
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
/.idea
/coverage.txt
/*.iml
/.env*
/.dev*
/.*cover.out*
/overalls*
profile.coverprofile
/public_html
/build
/var
/TODO
/gin-bin
/.vscode
/federation/etc/.env*
# https://github.com/nektos/act
/.act*
/workflow
/profiles

45
server/Dockerfile Normal file
View File

@@ -0,0 +1,45 @@
# deploy stage
FROM ubuntu:22.04
RUN apt-get -y update \
&& apt-get -y install \
ca-certificates \
curl \
&& rm -rf /var/lib/apt/lists/*
ARG VERSION=2022.9
ARG SASS_VERSION=1.69.5
ARG SERVER_VERSION=${VERSION}
ARG CORTEZA_SERVER_PATH=https://releases.cortezaproject.org/files/corteza-server-${SERVER_VERSION}-linux-amd64.tar.gz
ARG SASS_URL=https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz
RUN mkdir /tmp/server
ADD $CORTEZA_SERVER_PATH /tmp/server
VOLUME /data
RUN tar -zxvf "/tmp/server/$(basename $CORTEZA_SERVER_PATH)" -C / && \
rm -rf "/tmp/server" && \
mv /corteza-server /corteza
WORKDIR /opt
RUN curl -sOL $SASS_URL
RUN tar -xzf dart-sass-${SASS_VERSION}-linux-x64.tar.gz
WORKDIR /corteza
HEALTHCHECK --interval=30s --start-period=1m --timeout=30s --retries=3 \
CMD curl --silent --fail --fail-early http://127.0.0.1:80/healthcheck || exit 1
ENV STORAGE_PATH "/data"
ENV CORREDOR_ADDR "corredor:80"
ENV HTTP_ADDR "0.0.0.0:80"
ENV HTTP_WEBAPP_ENABLED "false"
ENV PATH "/opt/dart-sass:/corteza/bin:${PATH}"
EXPOSE 80
ENTRYPOINT ["./bin/corteza-server"]
CMD ["serve-api"]

View File

@@ -0,0 +1,52 @@
# bundle web-console
FROM node:16.14-alpine as webconsole-build-stage
WORKDIR /webconsole
COPY ./webconsole ./
# Snapshot is built in development mode and with source map
RUN yarn install && yarn build --mode dev --sourcemap
# build server
FROM golang:1.19-buster as server-build-stage
ENV BUILD_OS=linux
ENV BUILD_ARCH=amd64
ENV BUILD_VERSION=latest
WORKDIR /corteza
COPY . ./
COPY --from=webconsole-build-stage /webconsole/dist ./webconsole/dist
RUN make release-clean release
# deploy stage
FROM ubuntu:22.04
RUN apt-get -y update \
&& apt-get -y install \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ENV STORAGE_PATH "/data"
ENV CORREDOR_ADDR "corredor:80"
ENV HTTP_ADDR "0.0.0.0:80"
ENV HTTP_WEBAPP_ENABLED "false"
ENV PATH "/corteza/bin:${PATH}"
WORKDIR /corteza
VOLUME /data
COPY --from=server-build-stage /corteza/build/pkg/corteza-server ./
HEALTHCHECK --interval=30s --start-period=1m --timeout=30s --retries=3 \
CMD curl --silent --fail --fail-early http://127.0.0.1:80/healthcheck || exit 1
EXPOSE 80
ENTRYPOINT ["./bin/corteza-server"]
CMD ["serve-api"]

243
server/Makefile Normal file
View File

@@ -0,0 +1,243 @@
.PHONY: pack build help qa critic vet codegen provision docs build auth webapp
include Makefile.inc
BUILD_FLAVOUR ?= corteza
BUILD_TIME ?= $(shell date +%FT%T%z)
BUILD_VERSION ?= $(shell git describe --tags --abbrev=0)
BUILD_ARCH ?= $(shell $(GO) env GOARCH)
BUILD_OS ?= $(shell $(GO) env GOOS)
BUILD_OS_is_windows = $(filter windows,$(BUILD_OS))
BUILD_DEST_DIR ?= build
BUILD_NAME = $(BUILD_FLAVOUR)-server-$(BUILD_VERSION)-$(BUILD_OS)-$(BUILD_ARCH)
BUILD_BIN_NAME = $(BUILD_NAME)$(if $(BUILD_OS_is_windows),.exe,)
RELEASE_BASEDIR = $(BUILD_DEST_DIR)/pkg/$(BUILD_FLAVOUR)-server
RELEASE_NAME = $(BUILD_NAME).tar.gz
RELEASE_EXTRA_FILES ?= README.md LICENSE CONTRIBUTING.md DCO .env.example
RELEASE_PKEY ?= .upload-rsa
LDFLAGS_VERSION = -X github.com/cortezaproject/corteza/server/pkg/version.Version=$(BUILD_VERSION)
LDFLAGS_EXTRA ?=
LDFLAGS = -ldflags "$(LDFLAGS_VERSION) $(LDFLAGS_EXTRA)"
# Run go test cmd with flags, eg:
# $> make test.integration TEST_FLAGS="-v"
# $> make test.integration TEST_FLAGS="-v -run SpecialTest"
TEST_FLAGS ?=
COVER_MODE ?= count
COVER_PROFILE ?= .cover.out
COVER_FLAGS ?= -covermode=$(COVER_MODE) -coverprofile=$(COVER_PROFILE)
# Cover package maps for tests tasks
COVER_PKGS_system = ./system/...
COVER_PKGS_compose = ./compose/...
COVER_PKGS_federation = ./federation/...
COVER_PKGS_automation = ./automation/...
COVER_PKGS_pkg = ./pkg/...
COVER_PKGS_all = $(COVER_PKGS_pkg),$(COVER_PKGS_system),$(COVER_PKGS_compose),$(COVER_PKGS_federation),$(COVER_PKGS_automation)
COVER_PKGS_integration = $(COVER_PKGS_all)
TEST_SUITE_app = ./app/...
TEST_SUITE_pkg = ./pkg/...
TEST_SUITE_services = ./compose/... ./system/... ./federation/... ./auth/... ./automation/...
TEST_SUITE_unit = $(TEST_SUITE_pkg) $(TEST_SUITE_app) $(TEST_SUITE_services)
TEST_SUITE_integration = ./tests/...
TEST_SUITE_store = ./store/tests/...
TEST_SUITE_all = $(TEST_SUITE_unit) $(TEST_SUITE_integration) $(TEST_SUITE_store)
# Dev Support apps settings
DEV_MINIO_PORT ?= 9000
DEV_MAILHOG_SMTP_ADDR ?= 1025
DEV_MAILHOG_HTTP_ADDR ?= 8025
GIN_ARG_LADDR ?= localhost
GIN_ARGS ?= --laddr $(GIN_ARG_LADDR) --build cmd/corteza --immediate --bin build/gin-bin
GIN_CORTEZA_ENV_FILE ?= .env
GIN_CORTEZA_BIN_ARGS ?= --env-file $(GIN_CORTEZA_ENV_FILE)
DOCKER ?= docker
help:
@echo ""
@echo " Usage: make [target]"
@echo ""
@echo " - build build all apps"
@echo " - build.<app> build a specific app"
@echo " - vet run go vet on all code"
@echo " - critic run go critic on all code"
@echo " - test.all run all tests"
@echo " - test.unit run all unit tests"
@echo " - test.integration run all integration tests"
@echo ""
@echo " See tests/README.md for more info on running tests"
@echo ""
########################################################################################################################
# Building & packing
build: $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME)
$(BUILD_DEST_DIR)/$(BUILD_BIN_NAME):
GOOS=$(BUILD_OS) GOARCH=$(BUILD_ARCH) $(GO) build $(LDFLAGS) -o $@ cmd/corteza/main.go
release: build $(BUILD_DEST_DIR)/$(RELEASE_NAME)
$(BUILD_DEST_DIR)/$(RELEASE_NAME):
@ mkdir -p $(RELEASE_BASEDIR) $(RELEASE_BASEDIR)/bin
@ cp $(RELEASE_EXTRA_FILES) $(RELEASE_BASEDIR)/
@ cp -r provision $(RELEASE_BASEDIR)
@ rm -f $(RELEASE_BASEDIR)/provision/README.adoc $(RELEASE_BASEDIR)/provision/update.sh
@ cp $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME) $(RELEASE_BASEDIR)/bin/$(BUILD_FLAVOUR)-server
tar -C $(dir $(RELEASE_BASEDIR)) -czf $(BUILD_DEST_DIR)/$(RELEASE_NAME) $(notdir $(RELEASE_BASEDIR))
release-clean:
rm -rf $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME)
rm -rf $(BUILD_DEST_DIR)/$(RELEASE_NAME)
upload: $(RELEASE_PKEY)
@ echo "put $(BUILD_DEST_DIR)/*.tar.gz" | sftp -q -o "StrictHostKeyChecking no" -i $(RELEASE_PKEY) $(RELEASE_SFTP_URI)
@ rm -f $(RELEASE_PKEY)
$(RELEASE_PKEY):
@ echo $(RELEASE_SFTP_KEY) | base64 -d > $@
@ chmod 0400 $@
########################################################################################################################
# Development
watch: $(GIN)
$(GIN) $(GIN_ARGS) -- $(GIN_CORTEZA_BIN_ARGS) serve
realize: watch # BC
mailhog.up:
$(DOCKER) run --rm --publish $(DEV_MAILHOG_HTTP_ADDR):8025 --publish $(DEV_MAILHOG_SMTP_ADDR):1025 mailhog/mailhog
minio.up:
# Runs temp minio server
# No volume mounts because we do not want the data to persist
$(DOCKER) run --rm --publish $(DEV_MINIO_PORT):9000 --env-file .env minio/minio server /data
# Development helper - reruns test when files change
#
# make watch.test.unit
# make watch.test.pkg
# make watch.test.all
# make watch.test.pkg TEST_FLAGS="-v"
watch.test.%: $(FSWATCH)
( make test.$* || exit 0 ) && ( $(FSWATCH) -o . | xargs -n1 -I{} make test.$* )
watch.test: watch.test.unit
# See codegen/README.md for details
codegen:
@ make -C codegen all
cue.fmt: $(CUE)
$(CUE) fmt -v codegen/*.cue
$(CUE) fmt -v codegen/schema/*.cue
$(CUE) fmt -v app/*.cue
$(CUE) fmt -v app/options/*.cue
$(CUE) fmt system/*.cue
$(CUE) fmt compose/*.cue
$(CUE) fmt automation/*.cue
$(CUE) fmt federation/*.cue
codegen-legacy: $(CODEGEN)
@ $(CODEGEN) -v
watch.codegen-legacy: $(CODEGEN)
@ $(CODEGEN) -w -v
clean.codegen-legacy:
rm -f $(CODEGEN)
provision:
$(MAKE) --directory=provision clean all
webapp:
@ $(MAKE) --directory=webapp
vendors.check: $(MODOUTDATED)
$(GO) list -mod=mod -u -m -json all | $(MODOUTDATED) -update -direct
#######################################################################################################################
# Quality Assurance
# Adds -coverprofile flag to test flags
# and executes test.cover... task
test.coverprofile.%:
@ TEST_FLAGS="$(TEST_FLAGS) -coverprofile=$(COVER_PROFILE)" make test.cover.$*
# Adds -coverpkg flag
test.cover.%:
@ TEST_FLAGS="$(TEST_FLAGS) -coverpkg=$(COVER_PKGS_$*)" make test.$*
# Runs integration tests
test.integration: $(GOTEST)
$(GOTEST) $(TEST_FLAGS) $(TEST_SUITE_integration)
# Runs one suite from integration tests
test.integration.%: $(GOTEST)
$(GOTEST) $(TEST_FLAGS) ./tests/$*/...
# Runs store tests
test.store: $(GOTEST)
$(GOTEST) $(TEST_FLAGS) $(TEST_SUITE_store)
# Runs ALL tests
test.all: $(GOTEST)
$(GOTEST) $(TEST_FLAGS) $(TEST_SUITE_all)
# Unit testing testing, system or compose
test.unit.%: $(GOTEST)
$(GOTEST) $(TEST_FLAGS) ./$*/...
# Runs ALL tests
test.unit: $(GOTEST)
$(GOTEST) $(TEST_FLAGS) $(TEST_SUITE_unit)
# Testing pkg
test.pkg: $(GOTEST)
$(GOTEST) $(TEST_FLAGS) $(TEST_SUITE_pkg)
# Test defaults to test.unit
test: test.unit
vet:
$(GO) vet ./...
critic: $(GOCRITIC)
$(GOCRITIC) check -disable='commentFormatting,captLocal' ./...
staticcheck: $(STATICCHECK)
$(STATICCHECK) ./pkg/... ./system/... ./compose/... ./automation/...
qa: vet critic test
mocks: $(MOCKGEN)
$(MOCKGEN) -package mail -source pkg/mail/mail.go -destination pkg/mail/mail_mock_test.go
########################################################################################################################
# Toolset
# @todo this will most likely need some special care for other platforms
$(FSWATCH):
ifeq ($(UNAME_S),Darwin)
brew install fswatch
endif
# https://grpc.io/docs/protoc-installation/
# @todo $ apt install -y protobuf-compiler
$(PROTOC):
ifeq ($(UNAME_S),Darwin)
brew install protobuf
endif
#

90
server/Makefile.inc Normal file
View File

@@ -0,0 +1,90 @@
GO = go
GOINSTALL = $(GO) install
GOFLAGS ?= -trimpath
GOPATH ?= $(HOME)/go
ROOT_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
CGO_ENABLED = 1
export GOFLAGS
export CGO_ENABLED
########################################################################################################################
# Tool bins
GOCRITIC = $(GOPATH)/bin/gocritic
MOCKGEN = $(GOPATH)/bin/mockgen
GOTEST = $(GOPATH)/bin/gotest
STATICCHECK = $(GOPATH)/bin/staticcheck
PROTOGEN = $(GOPATH)/bin/protoc-gen-go
PROTOGEN_GRPC = $(GOPATH)/bin/protoc-gen-go-grpc
GIN = $(GOPATH)/bin/gin
STATIK = $(GOPATH)/bin/statik
MODOUTDATED = $(GOPATH)/bin/go-mod-outdated
CUE = $(GOPATH)/bin/cue
CODEGEN = $(GOPATH)/bin/corteza-codegen
JSONTPLEXEC = $(GOPATH)/bin/corteza-json-tpl-exec
PROTOC = /usr/local/bin/protoc
FSWATCH = /usr/local/bin/fswatch
# fswatch is intentionally left out...
BINS = $(GOCRITIC) $(MOCKGEN) $(GOTEST) $(STATICCHECK) $(PROTOGEN) $(GIN) $(STATIK)
# Corteza's tools
CORTEZA_BINS = $(CODEGEN) $(JSONTPLEXEC)
########################################################################################################################
# Go Toolset
$(GOCRITIC):
$(GOINSTALL) github.com/go-critic/go-critic/cmd/gocritic@latest
$(MOCKGEN):
$(GOINSTALL) github.com/golang/mock/mockgen@latest
$(STATICCHECK):
$(GOINSTALL) honnef.co/go/tools/cmd/staticcheck@latest
$(PROTOGEN):
$(GOINSTALL) github.com/golang/protobuf/protoc-gen-go@latest
$(PROTOGEN_GRPC):
$(GOINSTALL) google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
$(GIN):
$(GOINSTALL) github.com/codegangsta/gin@latest
$(GOTEST):
$(GOINSTALL) github.com/rakyll/gotest@latest
$(STATIK):
$(GOINSTALL) github.com/goware/statik@latest
$(MODOUTDATED):
$(GOINSTALL) github.com/psampaz/go-mod-outdated@latest
$(CUE):
$(GOINSTALL) cuelang.org/go/cmd/cue@v0.4.2
$(CODEGEN):
$(GO) build -o $@ $(ROOT_DIR)/cmd/codegen/main.go
$(JSONTPLEXEC):
$(GO) build -o $@ $(ROOT_DIR)/codegen/tool
clean-tools:
rm -f $(BINS)
clean-corteza-tools:
rm -f $(CORTEZA_BINS)
########################################################################################################################
COLOUR_GREEN=\033[0;32m
COLOUR_YELLOW=\033[0;33m
COLOUR_RED=\033[0;31m
COLOUR_BLUE=\033[0;34m
COLOUR_END=\033[0m
#

56
server/app/app.cue Normal file
View File

@@ -0,0 +1,56 @@
package app
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
"github.com/cortezaproject/corteza/server/app/options"
"github.com/cortezaproject/corteza/server/system"
"github.com/cortezaproject/corteza/server/compose"
"github.com/cortezaproject/corteza/server/automation"
"github.com/cortezaproject/corteza/server/federation"
)
corteza: schema.#platform & {
"ident": "corteza"
"options": [
options.DB,
options.HTTPClient,
options.HTTPServer,
options.RBAC,
options.SCIM,
options.SMTP,
options.actionLog,
options.apigw,
options.auth,
options.corredor,
options.environment,
options.eventbus,
options.federation,
options.limit,
options.locale,
options.log,
options.messagebus,
options.monitor,
options.objectStore,
options.provision,
options.sentry,
options.template,
options.upgrade,
options.waitFor,
options.websocket,
options.workflow,
options.discovery,
options.attachment,
options.webapp,
]
// platform resources
"resources": resources
"components": [
system.component,
compose.component,
automation.component,
federation.component,
]
}

92
server/app/app.go Normal file
View File

@@ -0,0 +1,92 @@
package app
import (
"context"
"net/http"
"github.com/cortezaproject/corteza/server/auth/settings"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/go-chi/chi/v5"
"github.com/go-oauth2/oauth2/v4"
"github.com/spf13/cobra"
"go.uber.org/zap"
"google.golang.org/grpc"
)
type (
httpApiServer interface {
Serve(ctx context.Context)
Activate(mm ...func(chi.Router))
Shutdown()
}
grpcServer interface {
RegisterServices(func(server *grpc.Server))
Serve(ctx context.Context)
}
wsServer interface {
MountRoutes(chi.Router)
Send(kind string, payload interface{}, userIDs ...uint64) error
}
authServicer interface {
MountHttpRoutes(string, chi.Router)
WellKnownOpenIDConfiguration() http.HandlerFunc
UpdateSettings(*settings.Settings)
Watch(ctx context.Context)
}
apigwServicer interface {
http.Handler
}
CortezaApp struct {
Opt *options.Options
lvl int
Log *zap.Logger
// Store interface
//
// Just a blank interface{} because we want to avoid generating
// whole store interface (as we do for other packages).
//
// Value will be type-casted when assigned to sys/msg/cmp services
// with warnings when incompatible
Store store.Storer
// CLI Commands
Command *cobra.Command
oa2m oauth2.Manager
DefaultAuthClient *types.AuthClient
// Servers
HttpServer httpApiServer
GrpcServer grpcServer
WsServer wsServer
AuthService authServicer
ApigwService apigwServicer
systemEntitiesInitialized bool
}
)
func New() *CortezaApp {
app := &CortezaApp{
lvl: bootLevelWaiting,
Log: logger.Default(),
}
app.InitCLI()
return app
}
func (app *CortezaApp) Options() *options.Options {
return app.Opt
}

159
server/app/boot_auth.go Normal file
View File

@@ -0,0 +1,159 @@
package app
import (
"context"
"fmt"
authHandlers "github.com/cortezaproject/corteza/server/auth/handlers"
"github.com/cortezaproject/corteza/server/auth/oauth2"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
"go.uber.org/zap"
"io/ioutil"
"strings"
)
func (app *CortezaApp) initAuth(ctx context.Context) (err error) {
log := app.Log.Named("auth")
if app.Opt.Auth.DefaultClient != "" {
// default client will help streamline authorization with default clients
app.DefaultAuthClient, err = store.LookupAuthClientByHandle(ctx, app.Store, app.Opt.Auth.DefaultClient)
if err != nil {
return fmt.Errorf("cannot load default client: %w", err)
}
}
app.oa2m = oauth2.NewManager(
app.Opt.Auth,
app.Log,
oauth2.NewClientStore(app.Store, app.DefaultAuthClient),
oauth2.NewTokenStore(app.Store),
)
// set base path for links&routes in auth server
authHandlers.BasePath = app.Opt.HTTPServer.BaseUrl
// set webapp base path in auth server
// @todo refactor, consider passing this into the functions that need it instead of setting it globally
authHandlers.WebappBasePath = app.Opt.HTTPServer.WebappBaseUrl
auth.DefaultSigner = auth.HmacSigner(app.Opt.Auth.Secret)
if auth.HttpTokenVerifier, err = auth.TokenVerifierMiddlewareWithSecretSigner(app.Opt.Auth.Secret); err != nil {
return fmt.Errorf("could not set token verifier")
}
// create token signature function from AUTH_ options
// and pass it on to the token issuer
alg, key, err := prepareSignatureFnParams(app.Opt.Auth)
if err != nil {
return fmt.Errorf("could not initialize token signer: %w", err)
}
log.Info(
"initializing JWT and authentication procedures",
zap.Stringer("algoritm", alg),
)
// construct token issuer with algorithm, secrets,
auth.TokenIssuer, err = auth.NewTokenIssuer(
auth.WithSigner(func(t jwt.Token) ([]byte, error) {
return jwt.Sign(t, alg, key)
}),
// @todo implement configurable issuer claim
//auth.WithDefaultIssuer(app.Opt.Auth.TokenClaimIssuer),
auth.WithDefaultExpiration(app.Opt.Auth.AccessTokenLifetime),
auth.WithDefaultClientID(app.DefaultAuthClient.ID),
auth.WithLookup(func(ctx context.Context, accessToken string) (err error) {
_, err = store.LookupAuthOa2tokenByAccess(ctx, app.Store, accessToken)
return err
}),
auth.WithStore(func(ctx context.Context, req auth.TokenRequest) error {
var (
eti = auth.GetExtraReqInfoFromContext(ctx)
createdAt = req.IssuedAt
oa2t = &types.AuthOa2token{
ID: id.Next(),
Access: req.AccessToken,
Refresh: req.RefreshToken,
CreatedAt: createdAt,
RemoteAddr: eti.RemoteAddr,
UserAgent: eti.UserAgent,
ClientID: req.ClientID,
UserID: req.UserID,
ExpiresAt: createdAt.Add(req.Expiration),
}
)
return store.CreateAuthOa2token(ctx, app.Store, oa2t)
}),
)
if err != nil {
return fmt.Errorf("could not initialize token issuer: %w", err)
}
return
}
// helper function that loads and/or parses private keys and initializes JWT signer function
// from the given arguments
func prepareSignatureFnParams(opt options.AuthOpt) (alg jwa.SignatureAlgorithm, key any, err error) {
alg = jwa.SignatureAlgorithm(opt.JwtAlgorithm)
switch alg {
case jwa.HS256, jwa.HS384, jwa.HS512:
// expecting secret to be set
if len(opt.Secret) == 0 {
return alg, nil, fmt.Errorf("token secret missing")
}
key = []byte(opt.Secret)
case
jwa.PS256, jwa.PS384, jwa.PS512,
jwa.RS256, jwa.RS384, jwa.RS512:
if len(opt.JwtKey) == 0 {
return alg, nil, fmt.Errorf("token key missing")
}
// if given key dos not begins with "-----BEGIN"
// assume it's path to a file and load contents of that file
if !strings.HasPrefix(opt.JwtKey, "-----BEGIN") {
var (
keyFile = opt.JwtKey
b []byte
)
if b, err = ioutil.ReadFile(keyFile); err != nil {
return alg, nil, fmt.Errorf("could not read key file: %w", err)
}
// overwrite th input and load
opt.JwtKey = string(b)
// recheck contents of the key
if !strings.HasPrefix(opt.JwtKey, "-----BEGIN") {
return alg, nil, fmt.Errorf("file %q does not contain a valid private key", keyFile)
}
}
// generates pem.Private from the kInput string
key, err = jwk.ParseKey([]byte(opt.JwtKey), jwk.WithPEM(true))
case "":
// should be caught by options init procedure and set to default,
// but you never know...
err = fmt.Errorf("token signature algorithm empty or missing")
default:
err = fmt.Errorf("token signature algorithm %q not supported", alg)
}
return
}

View File

@@ -0,0 +1,143 @@
package app
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/lestrrat-go/jwx/jwa"
"github.com/stretchr/testify/require"
"testing"
)
func TestPrepareSignatureFnParams(t *testing.T) {
type (
test struct {
name string
opt options.AuthOpt
err error
}
)
var (
privateKey = string(genKey())
)
tests := []test{
{
name: "empty algo",
opt: options.AuthOpt{JwtKey: "foobar"},
err: fmt.Errorf("token signature algorithm empty or missing"),
},
{
name: "unknown algo",
opt: options.AuthOpt{JwtAlgorithm: "foobar", JwtKey: "foobar"},
err: fmt.Errorf("token signature algorithm \"foobar\" not supported"),
},
{
name: "empty key",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS256.String()},
err: fmt.Errorf("token secret missing"),
},
{
name: "empty key",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS256.String()},
err: fmt.Errorf("token key missing"),
},
{
// "shared secret" string
name: "HS256",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS256.String(), Secret: "test key"},
},
{
// "shared secret" string
name: "HS384",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS384.String(), Secret: "test key"},
},
{
// "shared secret" string
name: "HS512",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS512.String(), Secret: "test key"},
},
{
// requires private key
name: "PS256",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS256.String(), JwtKey: privateKey},
},
{
// requires private key
name: "PS384",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS384.String(), JwtKey: privateKey},
},
{
// requires private key
name: "PS512",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS512.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS256",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS256.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS384",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS384.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS512",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS512.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS512 from a file",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS512.String(), JwtKey: "test_files/key.pem"},
},
{
// requires private key
name: "RS512 from a non-existing file",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS512.String(), JwtKey: "test_files/not-here.pem"},
err: fmt.Errorf("could not read key file: open test_files/not-here.pem: no such file or directory"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var (
req = require.New(t)
)
alg, key, err := prepareSignatureFnParams(tt.opt)
if tt.err != nil {
req.EqualError(err, tt.err.Error())
return
}
req.NoError(err)
req.NotEmpty(alg)
req.NotEmpty(key)
})
}
}
func genKey() []byte {
bitSize := 4096
// Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
panic(err)
}
// Encode private key to PKCS#1 ASN.1 PEM.
return pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
},
)
}

85
server/app/boot_dal.go Normal file
View File

@@ -0,0 +1,85 @@
package app
import (
"context"
"fmt"
"time"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"go.uber.org/zap"
)
func (app *CortezaApp) initDAL(ctx context.Context, log *zap.Logger) (err error) {
// no-op - if DAL is already initialized
if dal.Initialized() {
return
}
// Verify that primary store is connected
// or return error
if app.Store == nil {
return fmt.Errorf("primary store not connected")
}
var (
conn *types.DalConnection
cw *dal.ConnectionWrap
)
// load/create primary connection
if conn, err = provisionPrimaryDalConnection(ctx, app.Store); err != nil {
return fmt.Errorf("could not provision primary connection: %w", err)
}
// Convert connection to dal.ConnectionWrap
if cw, err = service.MakeDalConnection(conn, app.Store.ToDalConn()); err != nil {
return fmt.Errorf("could not convert connection: %w", err)
}
// Init DAL and prepare default connection
dal.SetGlobal(dal.New(log.Named("dal"), app.Opt.Environment.IsDevelopment()))
if err = dal.Service().ReplaceConnection(ctx, cw, true); err != nil {
return fmt.Errorf("could not set primary connection: %w", err)
}
return
}
// Creates entry for primary connection in the store
func provisionPrimaryDalConnection(ctx context.Context, s store.DalConnections) (conn *types.DalConnection, err error) {
conn, err = store.LookupDalConnectionByHandle(ctx, s, types.DalPrimaryConnectionHandle)
if err != nil && err != store.ErrNotFound {
return
}
// Already exists
if conn != nil {
return
}
conn = &types.DalConnection{
// Using id.Next since we dropped "special" ids a while ago.
// If needed, use the handle
ID: id.Next(),
Handle: types.DalPrimaryConnectionHandle,
Type: types.DalPrimaryConnectionResourceType,
Meta: types.ConnectionMeta{
Name: "Primary Database",
},
Config: types.ConnectionConfig{
DAL: &types.ConnectionConfigDAL{
ModelIdent: "compose_record",
},
},
CreatedAt: time.Now(),
}
return conn, store.CreateDalConnection(ctx, s, conn)
}

53
server/app/boot_envoy.go Normal file
View File

@@ -0,0 +1,53 @@
package app
import (
"context"
automationEnvoy "github.com/cortezaproject/corteza/server/automation/envoy"
composeEnvoy "github.com/cortezaproject/corteza/server/compose/envoy"
"github.com/cortezaproject/corteza/server/pkg/envoyx"
systemEnvoy "github.com/cortezaproject/corteza/server/system/envoy"
"go.uber.org/zap"
)
func (app *CortezaApp) initEnvoy(ctx context.Context, log *zap.Logger) (err error) {
// no-op - if envoy is already initialized
if envoyx.Initialized() {
return
}
// Init envoy
svc := envoyx.New()
envoyx.SetGlobal(svc)
// Register all component decoders
svc.AddDecoder(envoyx.DecodeTypeURI,
composeEnvoy.YamlDecoder{},
systemEnvoy.YamlDecoder{},
automationEnvoy.YamlDecoder{},
)
svc.AddDecoder(envoyx.DecodeTypeStore,
composeEnvoy.StoreDecoder{},
systemEnvoy.StoreDecoder{},
automationEnvoy.StoreDecoder{},
)
// Register all component encoders
svc.AddEncoder(envoyx.EncodeTypeIo,
composeEnvoy.YamlEncoder{},
systemEnvoy.YamlEncoder{},
automationEnvoy.YamlEncoder{},
)
svc.AddEncoder(envoyx.EncodeTypeStore,
composeEnvoy.StoreEncoder{},
systemEnvoy.StoreEncoder{},
automationEnvoy.StoreEncoder{},
)
// - datasource encoders
svc.AddEncoder(envoyx.EncodeTypeIo,
composeEnvoy.CsvEncoder{},
)
return
}

14
server/app/boot_expr.go Normal file
View File

@@ -0,0 +1,14 @@
package app
import (
"context"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/rbac"
)
func (app *CortezaApp) InitExpr(ctx context.Context) (err error) {
expr.Init(rbac.AllFunctions, expr.AllFunctions)
return
}

1087
server/app/boot_levels.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
package app
import (
"context"
"net/http"
"testing"
"github.com/cortezaproject/corteza/server/auth/external"
authSettings "github.com/cortezaproject/corteza/server/auth/settings"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/require"
)
type (
authSettingsUpdaterMockedAuthService struct {
settings *authSettings.Settings
}
)
func (*authSettingsUpdaterMockedAuthService) MountHttpRoutes(string, chi.Router) {}
func (*authSettingsUpdaterMockedAuthService) WellKnownOpenIDConfiguration() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {}
}
func (*authSettingsUpdaterMockedAuthService) Watch(context.Context) {}
func (m *authSettingsUpdaterMockedAuthService) UpdateSettings(settings *authSettings.Settings) {
m.settings = settings
}
func Test_updateAuthSettings(t *testing.T) {
cc := []struct {
name string
in types.AppSettings
out authSettings.Settings
}{
{
"enabled internal auth",
func() (s types.AppSettings) {
s.Auth.Internal.Enabled = true
return
}(),
func() (s authSettings.Settings) {
s.LocalEnabled = true
return
}(),
},
{
"add external providers",
func() (s types.AppSettings) {
s.Auth.External.Providers = []*types.ExternalAuthProvider{
{ // skip
Enabled: false,
Handle: "disabled",
Key: "key",
Secret: "sec",
},
{ // skip
Enabled: true,
Handle: "malformed",
Key: "",
Secret: "",
},
{ // skip
Enabled: true,
Handle: external.OIDC_PROVIDER_PREFIX + "without-issuer",
Key: "key",
Secret: "sec",
},
{ // add
Enabled: true,
Handle: external.OIDC_PROVIDER_PREFIX + "with-issuer",
IssuerUrl: "issuer",
Key: "key",
Secret: "sec",
},
{ // add (w/o issuer)
Enabled: true,
Handle: "google",
Key: "key",
Secret: "sec",
},
{ // add
Enabled: true,
Handle: "github",
Key: "key",
Secret: "sec",
},
{ // skip
Enabled: false,
Handle: "linkedin",
Key: "key",
Secret: "sec",
},
{ // add (used only for API integration, not for identity)
Enabled: true,
Handle: "nylas",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageAPI},
},
}
return
}(),
func() (s authSettings.Settings) {
s.Providers = []authSettings.Provider{
{
Handle: external.OIDC_PROVIDER_PREFIX + "with-issuer",
IssuerUrl: "issuer",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageIdentity},
},
{
Handle: "google",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageIdentity},
},
{
Handle: "github",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageIdentity},
},
{
Handle: "nylas",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageAPI},
},
}
return
}(),
},
}
for _, c := range cc {
t.Run(c.name, func(t *testing.T) {
var (
req = require.New(t)
m = &authSettingsUpdaterMockedAuthService{}
)
updateAuthSettings(m, &c.in)
req.Equal(*m.settings, c.out)
})
}
}

158
server/app/cli.go Normal file
View File

@@ -0,0 +1,158 @@
package app
import (
"context"
"fmt"
"sync"
composeCommands "github.com/cortezaproject/corteza/server/compose/commands"
authCommands "github.com/cortezaproject/corteza/server/auth/commands"
federationCommands "github.com/cortezaproject/corteza/server/federation/commands"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/api/server"
"github.com/cortezaproject/corteza/server/pkg/cli"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/envoyx"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/store"
systemCommands "github.com/cortezaproject/corteza/server/system/commands"
"go.uber.org/zap"
)
// InitCLI function initializes basic Corteza subsystems
// and sets-up the command line interface
func (app *CortezaApp) InitCLI() {
var (
ctx = cli.Context()
// path to all environmental files (or locations with .env file)
// filled from flag values
envs []string
)
id.Init(ctx)
app.Command = cli.RootCommand(func() error {
return nil
})
// Environmental variables (from the env, files, see cli.LoadEnv) MUST be
// loaded at this point!
if len(envs) == 0 {
envs = []string{"."}
}
// Loading env after the rootcommand is added, so as not to break
// the Execute() if there is an error
if err := cli.LoadEnv(envs...); err != nil {
app.Log.Error("failed to load environmental variables", zap.Error(err))
return
}
app.Opt = options.Init()
app.Command.Flags().StringSliceVar(&envs, "env-file", nil,
"Load environmental variables from files and directories containing .env file.\n"+
"Values from loaded files DO NOT override existing variables from the environment.\n"+
"This flag can be used multiple times, values are loaded from all provided locations.\n"+
"If no paths are provided, corteza loads .env file from the current directory (equivalent to --env-file .)")
serveCmd := cli.ServeCommand(func() (err error) {
wg := &sync.WaitGroup{}
{ // @todo refactor wait-for out of HTTP API server.
// test http server options first
if err = server.Test(app.Opt); err != nil {
return err
}
app.HttpServer = server.New(app.Log, app.Opt)
wg.Add(1)
go func() {
app.HttpServer.Serve(actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_API_REST))
wg.Done()
}()
}
{
// @todo add other server/listeners here...
//wg.Add(1)
//go func(ctx context.Context) {
// grpcApi.Serve(actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_API_GRPC))
// wg.Done()
//}(ctx)
}
if err = app.Activate(ctx); err != nil {
cli.HandleError(fmt.Errorf("could not start Corteza: %w", err))
return nil
}
app.HttpServer.Activate(app.mountHttpRoutes)
// Wait for all servers to be done
wg.Wait()
app.HttpServer.Shutdown()
return nil
})
upgradeCmd := cli.UpgradeCommand(func() (err error) {
if err = app.InitStore(ctx); err != nil {
return
}
return
})
provisionCmd := cli.ProvisionCommand(func() (err error) {
if err = app.Provision(ctx); err != nil {
return
}
return
})
storeInit := func(ctx context.Context) (store.Storer, error) {
err := app.InitStore(ctx)
return app.Store, err
}
dalInit := func(ctx context.Context) (dal.FullService, error) {
err := app.initDAL(ctx, app.Log)
return dal.Service(), err
}
envoyInit := func(ctx context.Context) (svc *envoyx.Service, err error) {
err = app.initEnvoy(ctx, app.Log)
return envoyx.Global(), err
}
app.Command.AddCommand(
systemCommands.Users(ctx, app),
systemCommands.Roles(ctx, app),
systemCommands.RBAC(ctx, storeInit),
systemCommands.Sink(ctx, app),
systemCommands.Settings(ctx, app),
systemCommands.Import(ctx, storeInit, dalInit, envoyInit),
systemCommands.Export(ctx, storeInit, dalInit, envoyInit),
serveCmd,
upgradeCmd,
provisionCmd,
authCommands.Command(ctx, app, storeInit),
federationCommands.Sync(ctx, app),
composeCommands.Base(ctx, app),
cli.EnvCommand(),
cli.VersionCommand(),
)
}
func (app *CortezaApp) Execute() error {
return app.Command.Execute()
}

View File

@@ -0,0 +1,43 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
RBAC: schema.#optionsGroup & {
handle: "rbac"
title: "RBAC options"
options: {
log: {
type: "bool"
description: "Log RBAC related events and actions"
}
service_user: {}
bypass_roles: {
defaultValue: "super-admin"
description: """
Space delimited list of role handles.
These roles causes short-circuiting access control check and allowing all operations.
System will refuse to start if check-bypassing roles are also listed as authenticated or anonymous auto-assigned roles.
"""
}
authenticated_roles: {
defaultValue: "authenticated"
description: """
Space delimited list of role handles.
These roles are automatically assigned to authenticated user.
Memberships can not be managed for these roles.
System will refuse to start if roles listed here are also listed under anonymous roles
"""
}
anonymous_roles: {
defaultValue: "anonymous"
description: """
Space delimited list of role handles.
These roles are automatically assigned to anonymous user.
Memberships can not be managed for these roles.
"""
}
}
}

View File

@@ -0,0 +1,35 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
SCIM: schema.#optionsGroup & {
handle: "scim"
title: "SCIM Server"
// @todo remove explicitly defined expIdent and adjust the code
expIdent: "SCIM"
options: {
enabled: {
type: "bool"
description: "Enable SCIM subsystem"
}
base_URL: {
defaultValue: "/scim"
description: "Prefix for SCIM API endpoints"
}
secret: {
description: "Secret to use to validate requests on SCIM API endpoints"
}
external_id_as_primary: {
type: "bool"
description: "Use external IDs in SCIM API endpoints"
}
external_id_validation: {
defaultValue: "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
description: "Validates format of external IDs. Defaults to UUID"
}
}
}

View File

@@ -0,0 +1,45 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
SMTP: schema.#optionsGroup & {
handle: "smtp"
title: "Email sending"
// @todo remove explicitly defined expIdent and adjust the code
expIdent: "SMTP"
intro: """
Configure your local SMTP server or use one of the available providers.
These values are copied to settings when the server starts and can be managed from the administration console.
We recommend you remove these values after they are copied to settings.
If server detects difference between these options and settings, it shows a warning in the log on server start.
"""
options: {
host: {
description: "The SMTP server hostname."
}
port: {
type: "int"
description: "The SMTP post."
}
user: {
description: "The SMTP username."
}
pass: {
description: "The SMTP password."
}
from: {
description: "The SMTP `from` email parameter"
}
tls_insecure: {
type: "bool"
description: "Allow insecure (invalid, expired TLS certificates) connections."
}
tls_server_name: {}
}
}

View File

@@ -0,0 +1,23 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
actionLog: schema.#optionsGroup & {
handle: "action-log"
env: "ACTIONLOG"
options: {
enabled: {
type: "bool"
defaultGoExpr: "true"
}
debug: {
type: "bool"
}
workflow_functions_enabled: {
type: "bool"
}
}
title: "Actionlog"
}

View File

@@ -0,0 +1,59 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
apigw: schema.#optionsGroup & {
handle: "apigw"
imports: [
"\"time\"",
]
options: {
enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable API Gateway"
}
debug: {
type: "bool"
description: "Enable API Gateway debugging info"
}
log_enabled: {
type: "bool"
description: "Enable extra logging"
}
profiler_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable profiler"
}
profiler_global: {
type: "bool"
defaultGoExpr: "false"
description: "Profiler enabled for all routes"
}
log_request_body: {
type: "bool"
description: "Enable incoming request body output in logs"
}
proxy_enable_debug_log: {
type: "bool"
description: "Enable full debug log on requests / responses - warning, includes sensitive data"
}
proxy_follow_redirects: {
type: "bool"
defaultGoExpr: "true"
description: "Follow redirects on proxy requests"
}
proxy_outbound_timeout: {
type: "time.Duration"
description: "Outbound request timeout"
defaultGoExpr: "time.Second * 30"
defaultValue: "30s"
}
}
title: "API Gateway"
}

View File

@@ -0,0 +1,32 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
attachment: schema.#optionsGroup & {
handle: "attachment"
options: {
avatar_max_file_size: {
type: "int64"
defaultGoExpr: "1000000"
description: "Avatar image maximum upload size, default value is 1MB"
}
avatar_initials_font_path: {
defaultValue: "fonts/Poppins-Regular.ttf"
description: "Avatar initials font file path"
env: "AVATAR_INITIALS_FONT_PATH"
}
avatar_initials_background_color: {
defaultValue: "#F3F3F3"
description: "Avatar initials background color"
env: "AVATAR_INITIALS_BACKGROUND_COLOR"
}
avatar_initials_color: {
defaultValue: "#0B344E"
description: "Avatar initials text color"
env: "AVATAR_INITIALS_COLOR"
}
}
}

262
server/app/options/auth.cue Normal file
View File

@@ -0,0 +1,262 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
auth: schema.#optionsGroup & {
handle: "auth"
imports: [
"\"time\"",
]
options: {
log_enabled: {
type: "bool"
description: "Enable extra logging for authentication flows"
}
password_security: {
type: "bool"
defaultGoExpr: "true"
description: """
Password security allows you to disable constraints to which passwords must conform to.
[CAUTION]
====
Disabling password security can be useful for development environments as it removes the need for complex passwords.
Password security *should be enabled* on production environments to avoid security incidents
====
"""
}
jwt_algorithm: {
defaultGoExpr: "\"HS512\""
defaultValue: "HS512"
description: """
Algorithm to be use for JWT signature.
Supported values:
- HS256, HS384, HS512
- PS256, PS384, PS512,
- RS256, RS384, RS512
Provide shared secret string for HS256, HS384, HS512 and full private key or path to the file PS* and RS* algorithms.
"""
}
secret: {
defaultGoExpr: "getSecretFromEnv(\"jwt secret\")"
description: """
Secret used for signing JWT tokens.
Value is used only when HS256, HS384 or HS512 algorithm is used.
[IMPORTANT]
====
If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
Generated secret will change if you change any of these variables.
====
"""
env: "AUTH_JWT_SECRET"
}
jwt_key: {
description: """
Raw private key or absolute or relative path to the file containing one.
"""
}
access_token_lifetime: {
type: "time.Duration"
description: """
Lifetime of the access token. Should be shorter than lifetime of the refresh token.
"""
env: "AUTH_OAUTH2_ACCESS_TOKEN_LIFETIME"
defaultGoExpr: "time.Hour * 2"
defaultValue: "2h"
}
refresh_token_lifetime: {
type: "time.Duration"
description: """
Lifetime of the refresh token. Should be much longer than lifetime of the access token.
Refresh tokens are used to exchange expired access tokens with new ones.
"""
env: "AUTH_OAUTH2_REFRESH_TOKEN_LIFETIME"
defaultGoExpr: "time.Hour * 24 * 3"
defaultValue: "72h"
}
external_redirect_URL: {
description: """
Redirect URL to be sent with OAuth2 authentication request to provider
`provider` placeholder is replaced with the actual value when used.
"""
defaultGoExpr: "FullURL(\"/auth/external/{provider}/callback\")"
}
external_cookie_secret: {
description: """
Secret used for securing cookies
[IMPORTANT]
====
If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
Generated secret will change if you change any of these variables.
====
"""
defaultGoExpr: "getSecretFromEnv(\"external cookie secret\")"
}
base_URL: {
description: """
Frontend base URL. Must be an absolute URL, with the domain.
This is used for some redirects and links in auth emails.
"""
defaultGoExpr: "FullURL(\"/auth\")"
}
session_cookie_name: {
description: "Session cookie name"
defaultValue: "session"
}
session_cookie_path: {
description: "Session cookie path"
defaultGoExpr: "pathPrefix(\"/auth\")"
}
session_cookie_domain: {
defaultGoExpr: "GuessApiHostname()"
description: "Session cookie domain"
}
session_cookie_secure: {
type: "bool"
defaultGoExpr: "isSecure()"
description: "Defaults to true when HTTPS is used. Corteza will try to guess the this setting by"
}
session_lifetime: {
type: "time.Duration"
description: """
Maximum time user is allowed to stay idle when logged in without \"remember-me\" option and before session is expired.
Recommended value is between an hour and a day.
[IMPORTANT]
====
This affects only profile (/auth) pages. Using applications (admin, compose, ...) does not prolong the session.
====
"""
defaultGoExpr: "24 * time.Hour"
defaultValue: "24h"
}
session_perm_lifetime: {
type: "time.Duration"
description: """
Duration of the session in /auth lasts when user logs-in with \"remember-me\" option.
If set to 0, \"remember-me\" option is removed.
"""
defaultGoExpr: "360 * 24 * time.Hour"
defaultValue: "8640h"
}
garbage_collector_interval: {
type: "time.Duration"
description: "How often are expired sessions and tokens purged from the database"
defaultGoExpr: "15 * time.Minute"
defaultValue: "15min"
}
request_rate_limit: {
type: "int"
description: """
How many requests from a certain IP address are allowed in a time window.
Set to zero to disable
"""
defaultGoExpr: "60"
defaultValue: "60"
}
request_rate_window_length: {
type: "time.Duration"
defaultGoExpr: "time.Minute"
defaultValue: "1m"
description: "How many requests from a certain IP address are allowed in a time window"
}
csrf_secret: {
defaultGoExpr: "getSecretFromEnv(\"csrf secret\")"
description: """
Secret used for securing CSRF protection
[IMPORTANT]
====
If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
Generated secret will change if you change any of these variables.
====
"""
}
csrf_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable CSRF protection"
}
csrf_field_name: {
defaultValue: "same-site-authenticity-token"
description: "Form field name used for CSRF protection"
}
csrf_cookie_name: {
defaultValue: "same-site-authenticity-token"
description: "Cookie name used for CSRF protection"
}
default_client: {
defaultValue: "corteza-webapp"
description: """
Handle for OAuth2 client used for automatic redirect from /auth/oauth2/go endpoint.
This simplifies configuration for OAuth2 flow for Corteza Web applications as it removes
the need to supply redirection URL and client ID (oauth2/go endpoint does that internally)
"""
}
default_user_group: {
defaultValue: "default-root"
description: """
Default user group.
If default sub user group is not set, all users are assigned here.
Each user must belong to a user group if it wishes to use the system.
"""
}
default_sub_user_group: {
defaultValue: "default-sub-root"
description: """
Default sub-user group.
If default sub-user group is set, all users are assigned here.
Each user must belong to a user group if it wishes to use the system.
"""
}
assets_path: {
description: """
Path to js, css, images and template source files
When corteza starts, if path exists it tries to load template files from it.
When empty path is set (default value), embedded files are used.
"""
}
development_mode: {
type: "bool"
description: """
When enabled, corteza reloads template before every execution.
Enable this for debugging or when developing auth templates.
Should be disabled in production where templates do not change between server restarts.
"""
}
provision_super_user: {
description: """
When set, Corteza creates one or more users with the configured values using provided email as a password.
It skips existing (email, handle). All new users are assigned to all bypass roles.
When set in production, Corteza stops and reports an error
"""
}
}
title: "Authentication"
}

View File

@@ -0,0 +1,76 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
corredor: schema.#optionsGroup & {
handle: "corredor"
imports: [
"\"time\"",
]
options: {
enabled: {
type: "bool"
description: "Enable/disable Corredor integration"
}
addr: {
defaultValue: "localhost:50051"
description: "Hostname and port of the Corredor gRPC server."
}
max_backoff_delay: {
type: "time.Duration"
description: "Max delay for backoff on connection."
defaultGoExpr: "time.Minute"
defaultValue: "1m"
}
max_receive_message_size: {
type: "int"
defaultGoExpr: "2 << 23"
description: "Max message size that can be received."
}
default_exec_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Minute"
}
list_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Second * 2"
defaultValue: "2s"
}
list_refresh: {
type: "time.Duration"
defaultGoExpr: "time.Second * 5"
}
run_as_enabled: {
type: "bool"
defaultGoExpr: "true"
}
tls_cert_enabled: {
type: "bool"
env: "CORREDOR_CLIENT_CERTIFICATES_ENABLED"
}
tls_cert_path: {
defaultValue: "/certs/corredor/client"
env: "CORREDOR_CLIENT_CERTIFICATES_PATH"
}
tls_cert_cA: {
defaultValue: "ca.crt"
env: "CORREDOR_CLIENT_CERTIFICATES_CA"
}
tls_cert_private: {
defaultValue: "private.key"
env: "CORREDOR_CLIENT_CERTIFICATES_PRIVATE"
}
tls_cert_public: {
defaultValue: "public.crt"
env: "CORREDOR_CLIENT_CERTIFICATES_PUBLIC"
}
tls_server_name: {
env: "CORREDOR_CLIENT_CERTIFICATES_SERVER_NAME"
}
}
title: "Connection to Corredor"
}

17
server/app/options/db.cue Normal file
View File

@@ -0,0 +1,17 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
DB: schema.#optionsGroup & {
handle: "DB"
options: {
DSN: {
defaultValue: "sqlite3://file::memory:?cache=shared&mode=memory"
description: "Database connection string."
}
}
title: "Connection to data store backend"
}

View File

@@ -0,0 +1,51 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
discovery: schema.#optionsGroup & {
handle: "discovery"
options: {
enabled: {
type: "bool"
defaultGoExpr: "false"
description: "Enable discovery endpoints"
}
debug: {
type: "bool"
defaultGoExpr: "false"
description: "Enable discovery related activity info"
}
corteza_domain: {
type: "string"
description: "Indicates host of corteza compose webapp"
}
base_url: {
type: "string"
description: "Indicates host of corteza discovery server"
}
embeddings_enabled: {
type: "bool"
defaultGoExpr: "false"
description: "Enable discovery embeddings generation"
}
embeddings_dimension: {
type: "int"
description: "Embeddings dimension"
defaultGoExpr: "384"
}
hnsw_ef_construction: {
type: "int"
description: "HNSW ef construction parameter"
defaultGoExpr: "128"
}
hnsw_m: {
type: "int"
description: "HNSW m parameter"
defaultGoExpr: "16"
}
}
title: "Discovery"
}

View File

@@ -0,0 +1,16 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
environment: schema.#optionsGroup & {
handle: "environment"
options: {
environment: {
defaultValue: "production"
env: "ENVIRONMENT"
}
}
title: "Environment"
}

View File

@@ -0,0 +1,29 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
eventbus: schema.#optionsGroup & {
handle: "eventbus"
imports: [
"\"time\"",
]
options: {
scheduler_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable eventbus scheduler."
}
scheduler_interval: {
type: "time.Duration"
description: "Set time interval for `eventbus` scheduler."
defaultGoExpr: "time.Minute"
defaultValue: "60s"
}
}
title: "Events and scheduler"
}

View File

@@ -0,0 +1,58 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
federation: schema.#optionsGroup & {
handle: "federation"
imports: [
"\"time\"",
]
options: {
enabled: {
type: "bool"
description: "Federation enabled on system, it toggles rest API endpoints, possibility to map modules in Compose and sync itself"
}
label: {
type: "string"
defaultValue: "federated"
description: "Federation label"
}
host: {
type: "string"
defaultValue: "local.cortezaproject.org"
description: "Host that is used during node pairing, also included in invitation"
}
structure_monitor_interval: {
type: "time.Duration"
defaultGoExpr: "time.Minute * 2"
defaultValue: "2m"
description: "Delay in seconds for structure sync"
env: "FEDERATION_SYNC_STRUCTURE_MONITOR_INTERVAL"
}
structure_page_size: {
type: "int"
defaultGoExpr: "1"
defaultValue: "1"
description: "Bulk size in fetching for structure sync"
env: "FEDERATION_SYNC_STRUCTURE_PAGE_SIZE"
}
data_monitor_interval: {
type: "time.Duration"
defaultGoExpr: "time.Minute "
defaultValue: "1m"
description: "Delay in seconds for data sync"
env: "FEDERATION_SYNC_DATA_MONITOR_INTERVAL"
}
data_page_size: {
type: "int"
defaultGoExpr: "100"
defaultValue: "100"
description: "Bulk size in fetching for data sync"
env: "FEDERATION_SYNC_DATA_PAGE_SIZE"
}
}
}

View File

@@ -0,0 +1,39 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
HTTPClient: schema.#optionsGroup & {
title: "HTTP Client"
// Explicitly define all variants to be 100% compaltible with old name
handle: "http-client"
// @todo remove explcitly defined expIdent and adjust the code
expIdent: "HTTPClient"
imports: [
"\"time\"",
]
options: {
tls_insecure: {
type: "bool"
description: """
Allow insecure (invalid, expired TLS/SSL certificates) connections.
[IMPORTANT]
====
We strongly recommend keeping this value set to false except for local development or demos.
====
"""
}
timeout: {
type: "time.Duration"
description: "Default timeout for clients."
defaultGoExpr: "30 * time.Second"
defaultValue: "30s"
}
}
}

View File

@@ -0,0 +1,158 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
HTTPServer: schema.#optionsGroup & {
handle: "http-server"
title: "HTTP Server"
imports: [
"\"github.com/cortezaproject/corteza/server/pkg/rand\"",
]
options: {
domain: {
defaultValue: "localhost"
description: "Domain for the HTTP server."
env: "DOMAIN"
}
domainWebapp: {
defaultValue: "localhost"
description: "Domain for the HTTP webapp."
env: "DOMAIN_WEBAPP"
}
addr: {
defaultValue: ":80"
description: "IP and port for the HTTP server."
env: "HTTP_ADDR"
}
logRequest: {
type: "bool"
description: "Log HTTP requests."
env: "HTTP_LOG_REQUEST"
}
logResponse: {
type: "bool"
description: "Log HTTP responses."
env: "HTTP_LOG_RESPONSE"
}
tracing: {
type: "bool"
env: "HTTP_ERROR_TRACING"
}
enableHealthcheckRoute: {
type: "bool"
defaultGoExpr: "true"
env: "HTTP_ENABLE_HEALTHCHECK_ROUTE"
}
enableVersionRoute: {
type: "bool"
defaultGoExpr: "true"
description: "Enable `/version` route."
env: "HTTP_ENABLE_VERSION_ROUTE"
}
enableDebugRoute: {
type: "bool"
description: "Enable `/debug` route."
env: "HTTP_ENABLE_DEBUG_ROUTE"
}
enableMetrics: {
type: "bool"
description: "Enable (prometheus) metrics."
env: "HTTP_METRICS"
}
metricsServiceLabel: {
defaultValue: "corteza"
description: "Name for metrics endpoint."
env: "HTTP_METRICS_NAME"
}
metricsUsername: {
defaultValue: "metrics"
description: "Username for the metrics endpoint."
env: "HTTP_METRICS_USERNAME"
}
metricsPassword: {
defaultGoExpr: "string(rand.Bytes(5))"
description: "Password for the metrics endpoint."
env: "HTTP_METRICS_PASSWORD"
}
enablePanicReporting: {
type: "bool"
defaultGoExpr: "true"
description: "Report HTTP panic to Sentry."
env: "HTTP_REPORT_PANIC"
}
baseUrl: {
defaultValue: "/"
description: "Base URL (prefix) for all routes (<baseUrl>/auth, <baseUrl>/api, ...)"
env: "HTTP_BASE_URL"
}
apiEnabled: {
type: "bool"
defaultGoExpr: "true"
env: "HTTP_API_ENABLED"
}
apiBaseUrl: {
defaultValue: "/"
description: """
When webapps are enabled (HTTP_WEBAPP_ENABLED) this is moved to '/api' if not explicitly set otherwise.
API base URL is internally prefixed with baseUrl
"""
env: "HTTP_API_BASE_URL"
}
webappEnabled: {
type: "bool"
env: "HTTP_WEBAPP_ENABLED"
}
webappBaseUrl: {
defaultValue: "/"
description: "Webapp base URL is internally prefixed with baseUrl"
env: "HTTP_WEBAPP_BASE_URL"
}
webappBaseDir: {
defaultValue: "./webapp/public"
env: "HTTP_WEBAPP_BASE_DIR"
}
webappList: {
defaultValue: "admin,compose,workflow,reporter,privacy"
env: "HTTP_WEBAPP_LIST"
}
sslTerminated: {
type: "bool"
defaultGoExpr: "isSecure()"
description: """
Is SSL termination enabled in ingres, proxy or load balancer that is in front of Corteza?
By default, Corteza checks for presence of LETSENCRYPT_HOST environmental variable.
This DOES NOT enable SSL termination in Corteza!
"""
env: "HTTP_SSL_TERMINATED"
}
assets_path: {
description: """
Corteza will directly serve these assets (static files).
When empty path is set (default value), embedded files are used.
"""
}
web_console_enabled: {
type: "bool"
defaultGoExpr: "false"
description: "Enable web console. When running in dev environment, web console is enabled by default."
}
web_console_username: {
defaultValue: "admin"
description: "Username for the web console endpoint."
}
web_console_password: {
defaultGoExpr: "string(rand.Bytes(32))"
description: """
Password for the web console endpoint. When running in dev environment, password is not required.
Corteza intentionally sets default password to random chars to prevent security incidents.
"""
}
}
}

View File

@@ -0,0 +1,20 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
limit: schema.#optionsGroup & {
handle: "limit"
options: {
system_users: {
type: "int"
description: "Maximum number of valid (not deleted, not suspended) users"
}
record_count_per_module: {
type: "int"
description: "Maximum number of records per module"
}
}
title: "Limits"
}

View File

@@ -0,0 +1,59 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
locale: schema.#optionsGroup & {
handle: "locale"
options: {
languages: {
defaultValue: "en"
description: """
List of comma delimited languages (language tags) to enable.
In case when an enabled language can not be loaded, error is logged.
When loading language configurations (config.xml) from the configured path(s).
"""
}
path: {
description: """
One or more paths to locale config and translation files, separated by colon
When with LOCALE_DEVELOPMENT_MODE=true, default value for path is ../../locale
"""
}
query_string_param: {
defaultValue: "lng"
description: """
Name of the query string parameter used to pass the language tag (it overrides Accept-Language header).
Set it to empty string to disable detection from the query string.
This parameter is ignored if only one language is enabled
"""
}
resource_translations_enabled: {
type: "bool"
description: """
When enabled, an editor for resource translations is enabled in UI
"""
}
log: {
type: "bool"
description: "Log locale related events and actions"
}
development_mode: {
type: "bool"
description: """
When enabled, Corteza reloads language files on every request
Enable this for debugging or developing.
"""
}
}
}

View File

@@ -0,0 +1,67 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
log: schema.#optionsGroup & {
handle: "log"
options: {
debug: {
type: "bool"
description: """
Disables json format for logging and enables more human-readable output with colors.
Disable for production.
"""
}
level: {
defaultValue: "warn"
description: """
Minimum logging level. If set to "warn",
Levels warn, error, dpanic panic and fatal will be logged.
Recommended value for production: warn
Possible values: debug, info, warn, error, dpanic, panic, fatal
"""
}
filter: {
description: """
Log filtering rules by level and name (log-level:log-namespace).
Please note that level (LOG_LEVEL) is applied before filter and it affects the final output!
Leave unset for production.
Example:
`warn+:* *:auth,workflow.*`
Log warnings, errors, panic, fatals. Everything from auth and workflow is logged.
See more examples and documentation here: https://github.com/moul/zapfilter
"""
}
include_caller: {
type: "bool"
description: """
Set to true to see where the logging was called from.
Disable for production.
"""
}
stacktrace_level: {
defaultValue: "dpanic"
description: """
Include stack-trace when logging at a specified level or below.
Disable for production.
Possible values: debug, info, warn, error, dpanic, panic, fatal
"""
}
}
}

View File

@@ -0,0 +1,21 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
messagebus: schema.#optionsGroup & {
handle: "messagebus"
options: {
Enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable messagebus"
}
log_enabled: {
type: "bool"
description: "Enable extra logging for messagebus watchers"
}
}
title: "Messaging queue"
}

View File

@@ -0,0 +1,23 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
monitor: schema.#optionsGroup & {
handle: "monitor"
imports: [
"\"time\"",
]
options: {
interval: {
type: "time.Duration"
defaultGoExpr: "5 * time.Minute"
defaultValue: "5m"
description: "Output (log) interval for monitoring."
}
}
title: "Monitoring"
}

View File

@@ -0,0 +1,49 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
objectStore: schema.#optionsGroup & {
handle: "object-store"
title: "Object (file) storage"
intro: "The MinIO integration allows you to replace local storage with cloud storage. When configured, `STORAGE_PATH` is not needed."
options: {
path: {
defaultValue: "var/store"
description: "Location where uploaded files are stored."
env: "STORAGE_PATH"
}
minioEndpoint: {
env: "MINIO_ENDPOINT"
}
minioSecure: {
type: "bool"
defaultGoExpr: "true"
env: "MINIO_SECURE"
}
minioAccessKey: {
env: "MINIO_ACCESS_KEY"
}
minioSecretKey: {
env: "MINIO_SECRET_KEY"
}
minioSSECKey: {
env: "MINIO_SSEC_KEY"
}
minioBucket: {
defaultValue: "{component}"
description: "`component` placeholder is replaced with service name (e.g system)."
env: "MINIO_BUCKET"
}
minioPathPrefix: {
description: "`component` placeholder is replaced with service name (e.g system)."
env: "MINIO_PATH_PREFIX"
}
minioStrict: {
type: "bool"
env: "MINIO_STRICT"
}
}
}

View File

@@ -0,0 +1,32 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
provision: schema.#optionsGroup & {
handle: "provision"
options: {
always: {
type: "bool"
defaultGoExpr: "true"
description: "Controls if provision should run when the server starts."
}
path: {
defaultValue: "provision/*"
description: "Colon seperated paths to config files for provisioning."
}
}
title: "Provisioning"
intro: """
Provisioning allows you to configure a {PRODUCT_NAME} instance when deployed.
It occurs automatically after the {PRODUCT_NAME} server starts.
[IMPORTANT]
====
We recommend you to keep provisioning enabled as it simplifies version updates by updating the database and updating settings.
If you're doing local development or some debugging, you can disable this.
====
"""
}

View File

@@ -0,0 +1,65 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
sentry: schema.#optionsGroup & {
handle: "sentry"
imports: [
"\"github.com/cortezaproject/corteza/server/pkg/version\"",
]
title: "Sentry monitoring"
intro: """
[NOTE]
====
These parameters help in the development and testing process.
When you are deploying to production, these should be disabled to improve performance and reduce storage usage.
You should configure external services such as Sentry or ELK to keep track of logs and error reports.
====
"""
options: {
DSN: {
description: "Set to enable Sentry client."
}
debug: {
type: "bool"
description: "Print out debugging information."
}
attach_stacktrace: {
type: "bool"
defaultGoExpr: "true"
description: "Attach stacktraces"
}
sample_rate: {
type: "float64"
description: "Sample rate for event submission (0.0 - 1.0. defaults to 1.0)"
}
max_breadcrumbs: {
type: "int"
defaultGoExpr: "0"
description: "Maximum number of breadcrumbs."
}
server_name: {
description: "Set reported Server name."
env: "SENTRY_SERVERNAME"
}
release: {
defaultGoExpr: "version.Version"
description: "Set reported Release."
}
dist: {
description: "Set reported distribution."
}
environment: {
description: "Set reported environment."
}
webapp_DSN: {
description: "Set to enable Sentry client for webapp."
}
}
}

View File

@@ -0,0 +1,22 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
template: schema.#optionsGroup & {
handle: "template"
title: "Rendering engine"
options: {
renderer_gotenberg_address: {
defaultGoExpr: ""
description: "Gotenberg rendering container address."
}
renderer_gotenberg_enabled: {
type: "bool"
description: "Is Gotenberg rendering container enabled."
}
}
}

View File

@@ -0,0 +1,25 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
upgrade: schema.#optionsGroup & {
handle: "upgrade"
title: "Data store (database) upgrade"
options: {
debug: {
type: "bool"
description: """
Enable/disable debug logging.
To enable debug logging set `UPGRADE_DEBUG=true`.
"""
}
always: {
type: "bool"
defaultGoExpr: "true"
description: "Controls if the upgradable systems should be upgraded when the server starts."
}
}
}

View File

@@ -0,0 +1,76 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
waitFor: schema.#optionsGroup & {
handle: "wait-for"
imports: [
"\"time\"",
]
title: "Delay system startup"
intro: """
You can configure these options to defer API execution until another external (HTTP) service is up and running.
[ TIP ]
====
Delaying API execution can come in handy in complex setups where execution order is important.
====
"""
options: {
delay: {
type: "time.Duration"
description: """
Delays API startup for the amount of time specified (10s, 2m...).
This delay happens before service (`WAIT_FOR_SERVICES`) probing.
"""
env: "WAIT_FOR"
}
status_page: {
type: "bool"
defaultGoExpr: "true"
description: "Show temporary status web page."
env: "WAIT_FOR_STATUS_PAGE"
}
services: {
description: """
Space delimited list of hosts and/or URLs to probe.
Host format: `host` or `host:443` (port will default to 80).
[NOTE]
====
Services are probed in parallel.
====
"""
env: "WAIT_FOR_SERVICES"
}
services_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Minute"
defaultValue: "1m"
description: "Max time for each service probe."
env: "WAIT_FOR_SERVICES_TIMEOUT"
}
services_probe_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Second * 30"
defaultValue: "30s"
description: "Timeout for each service probe."
env: "WAIT_FOR_SERVICES_PROBE_TIMEOUT"
}
services_probe_interval: {
type: "time.Duration"
defaultGoExpr: "time.Second * 5"
defaultValue: "5s"
description: "Interval between service probes."
env: "WAIT_FOR_SERVICES_PROBE_INTERVAL"
}
}
}

View File

@@ -0,0 +1,15 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
webapp: schema.#optionsGroup & {
handle: "webapp"
options: {
scss_dir_path: {
description: "Path to custom SCSS source files directory"
}
}
}

View File

@@ -0,0 +1,39 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
websocket: schema.#optionsGroup & {
handle: "websocket"
imports: [
"\"time\"",
]
title: "Websocket server"
options: {
log_enabled: {
type: "bool"
description: "Enable extra logging for authentication flows"
}
timeout: {
type: "time.Duration"
description: "Time before `WsServer` gets timed out."
defaultGoExpr: "15 * time.Second"
defaultValue: "15s"
}
ping_timeout: {
type: "time.Duration"
defaultGoExpr: "120 * time.Second"
defaultValue: "120s"
}
ping_period: {
type: "time.Duration"
defaultGoExpr: "((120 * time.Second) * 9) / 10"
defaultValue: "119s"
}
}
}

View File

@@ -0,0 +1,36 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
workflow: schema.#optionsGroup & {
handle: "workflow"
options: {
register: {
type: "bool"
defaultGoExpr: "true"
description: "Registers enabled and valid workflows and executes them when triggered"
}
exec_debug: {
type: "bool"
description: "Enables verbose logging for workflow execution"
}
call_stack_size: {
type: "int"
defaultGoExpr: "16"
description: "Defines the maximum call stack size between workflows"
}
stack_trace_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enables execution stack trace construction"
}
stack_trace_full: {
type: "bool"
defaultGoExpr: "false"
description: "Forces the stack trace to record all steps; intended for testing, memory use grows with every executed step"
}
}
title: "Workflow"
}

17
server/app/rand.go Normal file
View File

@@ -0,0 +1,17 @@
package app
import (
cr "crypto/rand"
"encoding/binary"
"math/rand"
)
func init() {
// seed some randomness
var b [8]byte
if _, err := cr.Read(b[:]); err != nil {
panic(err)
}
rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

389
server/app/resources.cue Normal file
View File

@@ -0,0 +1,389 @@
package app
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
_allFeaturesDisabled: {
labels: false
paging: false
sorting: false
checkFn: false
}
resources: { [key=_]: {"handle": key, "component": "system", "platform": "corteza" } & schema.#PkgResource } & {
"rbac-rule": {
package: {
ident: "rbac"
import: "github.com/cortezaproject/corteza/server/pkg/rbac"
}
ident: "rule"
identPlural: "rules"
expIdent: "Rule"
features: _allFeaturesDisabled
model: {
ident: "rbac_rules"
attributes: {
role_id: {
goType: "uint64",
ident: "roleID",
storeIdent: "rel_role"
dal: { type: "Ref", refModelResType: "corteza::system:role" }
}
resource: {
dal: { length: 512 }
}
operation: {
dal: { length: 50 }
}
access: {
goType: "types.Access"
dal: { type: "Number", meta: { "rdbms:type": "integer" } }
}
}
indexes: {
"primary": { attributes: [ "role_id", "resource", "operation" ] }
}
}
store: {
ident: "rbacRule"
api: {
functions: [
{
expIdent: "TransferRbacRules"
args: [
{ident: "src", goType: "uint64"},
{ident: "dst", goType: "uint64"},
]
return: []
},
]
}
}
}
"label": {
package: {
ident: "labels"
import: "github.com/cortezaproject/corteza/server/pkg/label/types"
}
ident: "label"
identPlural: "labels"
expIdent: "Label"
features: _allFeaturesDisabled
model: {
attributes: {
kind: {
dal: { length: 64 }
}
resource_id: {
goType: "uint64",
ident: "resourceID",
storeIdent: "rel_resource"
dal: { type: "ID" }
}
name: {
ignoreCase: true
dal: { length: 512 }
}
value: {
goType: "types.LabelValue"
dal: { type: "JSON"}
omitSetter: true
omitGetter: true
}
}
indexes: {
"primary": { attributes: ["kind", "resource_id", "name"] }
"unique_kind_res_name": {
fields: [
{ attribute: "kind" },
{ attribute: "resource_id" },
{ attribute: "name", modifiers: [ "LOWERCASE" ] },
]
}
}
}
filter: {
expIdent: "LabelFilter"
struct: {
kind: {}
rel_resource: { goType: "[]uint64", ident: "resourceID" }
limit: { goType: "uint" }
}
byValue: ["kind" , "rel_resource", ]
}
store: {
api: {
lookups: [
{
fields: ["kind", "resource_id", "name"]
description: """
searches for label by kind, resource ID and name
"""
},
]
functions: [
{
expIdent: "DeleteExtraLabels"
args: [
{ident: "kind", goType: "string"},
{ident: "resourceId", goType: "uint64"},
{ident: "name", goType: "string", spread: true},
]
return: []
},
]
}
}
}
"flag": {
package: {
ident: "flag"
import: "github.com/cortezaproject/corteza/server/pkg/flag/types"
}
ident: "flag"
identPlural: "flags"
expIdent: "Flag"
features: _allFeaturesDisabled
model: {
attributes: {
kind: {
dal: { length: 64 }
}
resource_id: {
goType: "uint64",
ident: "resourceID",
storeIdent: "rel_resource"
dal: { type: "ID" }
}
owned_by: schema.AttributeUserRef
name: {
ignoreCase: true
dal: { length: 512 }
}
active: {
goType: "bool"
dal: { type: "Boolean" }
}
}
indexes: {
"primary": { attributes: ["kind", "resource_id", "owned_by", "name"] }
"unique_kind_res_owner_name": {
fields: [
{ attribute: "kind" },
{ attribute: "resource_id" },
{ attribute: "owned_by" },
{ attribute: "name", modifiers: [ "LOWERCASE" ] },
]
}
}
}
filter: {
expIdent: "FlagFilter"
struct: {
kind: {}
resource_id: { goType: "[]uint64",
ident: "resourceID", storeIdent: "rel_resource" }
owned_by: { goType: "[]uint64", ident: "ownedBy" }
name: { goType: "[]string", ident: "name" }
}
byValue: ["kind", "resource_id", "owned_by", "name", ]
}
store: {
api: {
lookups: [
{
fields: ["kind", "resource_id", "owned_by", "name"]
description: """
searches for flag by kind, resource ID, owner and name
"""
},
]
}
}
}
"actionlog": {
package: {
ident: "actionlog"
import: "github.com/cortezaproject/corteza/server/pkg/actionlog"
}
ident: "action"
identPlural: "action"
expIdent: "Action"
features: {
labels: false
paging: false
checkFn: false
}
model: {
ident: "actionlog"
attributes: {
id: schema.IdField
timestamp: schema.SortableTimestampField & { storeIdent: "ts" }
actor_ip_addr: {
ident: "actorIPAddr"
dal: { type: "Text", length: 64 }
}
actor_id: {
goType: "uint64",
ident: "actorID"
dal: { type: "Ref", refModelResType: "corteza::system:user" }
}
request_origin: {
dal: { type: "Text", length: 32 }
}
request_id: {
ident: "requestID"
dal: { type: "Text", length: 256 }
}
resource: {
dal: { type: "Text", length: 512 }
}
action: {
dal: { type: "Text", length: 64 }
}
error: {
dal: {}
}
severity: {
goType: "types.Severity"
dal: { type: "Number", default: 0, meta: { "rdbms:type": "integer" } }
}
description: {
dal: {}
}
meta: {
goType: "types.Meta"
dal: { type: "JSON", defaultEmptyObject: true }
}
}
indexes: {
"primary": { attribute: "id" }
"action": { attribute: "action"}
"actor_id": { attribute: "actor_id"}
"rel_resource": { attribute: "resource"}
"ts": { attribute: "timestamp"}
}
}
filter: {
expIdent: "Filter"
struct: {
from_timestamp: { goType: "*time.Time" }
to_timestamp: { goType: "*time.Time" }
before_action_id: { goType: "uint64", ident: "beforeActionID" }
actor_id: { goType: "[]uint64", ident: "actorID" }
origin: {}
resource: {}
action: {}
limit: { goType: "uint" }
}
byValue: ["action", "resource", "origin", "actor_id" ]
}
store: {
ident: "actionlog"
api: {
lookups: [
{
fields: ["id"]
description: """
searches for action log by ID
"""
},
]
}
}
}
"resource-activity": {
package: {
ident: "discovery"
import: "github.com/cortezaproject/corteza/server/discovery/types"
}
ident: "resourceActivity"
identPlural: "resourceActivities"
expIdent: "ResourceActivity"
expIdentPlural: "ResourceActivities"
features: _allFeaturesDisabled
model: {
ident: "resource_activity_log"
attributes: {
id: schema.IdField
timestamp: schema.SortableTimestampField & { storeIdent: "ts" }
resource_type: {
dal: {}
}
resource_action: {
dal: {}
}
resource_id: {
goType: "uint64",
ident: "resourceID",
storeIdent: "rel_resource"
dal: { type: "ID" }
}
meta: {
goType: "rawJson"
dal: { type: "JSON", defaultEmptyObject: true }
}
}
indexes: {
"primary": { attribute: "id" }
"resource": { attribute: "resource_id" }
}
}
filter: {
expIdent: "ResourceActivityFilter"
struct: {
from_timestamp: { goType: "*time.Time" }
to_timestamp: { goType: "*time.Time" }
}
}
store: {
ident: "resourceActivity"
api: {
lookups: []
}
}
}
}

163
server/app/servers.go Normal file
View File

@@ -0,0 +1,163 @@
package app
import (
"net/http"
"path"
"regexp"
"strings"
"github.com/cortezaproject/corteza/server/assets"
automationRest "github.com/cortezaproject/corteza/server/automation/rest"
composeRest "github.com/cortezaproject/corteza/server/compose/rest"
discoveryRest "github.com/cortezaproject/corteza/server/discovery/rest"
"github.com/cortezaproject/corteza/server/docs"
federationRest "github.com/cortezaproject/corteza/server/federation/rest"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/pkg/webapp"
systemRest "github.com/cortezaproject/corteza/server/system/rest"
"github.com/cortezaproject/corteza/server/system/scim"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
)
func (app *CortezaApp) mountHttpRoutes(r chi.Router) {
var (
ho = app.Opt.HTTPServer
)
func() {
// asset serving has some overlap with auth assets, web-console and webapp serving
// and might be joined with one or more of them in the later version
var (
url = options.CleanBase(ho.BaseUrl, "assets")
aPath = ho.AssetsPath
files = assets.Files(app.Log, aPath)
)
r.Handle(url+"/*", http.StripPrefix(url+"/", http.FileServer(http.FS(files))))
if aPath != "" {
app.Log.Info("custom web assets mounted", zap.String("url", url), zap.String("path", aPath))
} else {
app.Log.Info("embedded web assets mounted", zap.String("url", url))
}
}()
func() {
if ho.WebappEnabled && ho.ApiEnabled && ho.ApiBaseUrl == ho.WebappBaseUrl {
app.Log.
Warn("client web applications and api can not use the same base URL: '" + ho.WebappBaseUrl + "'")
ho.WebappEnabled = false
}
if !ho.WebappEnabled {
app.Log.Info("client web applications disabled")
return
}
r.Route(options.CleanBase(ho.WebappBaseUrl), webapp.MakeWebappServer(app.Log, ho, app.Opt.Auth, app.Opt.Discovery, app.Opt.Sentry))
app.Log.Info(
"client web applications enabled",
zap.String("baseUrl", options.CleanBase(ho.BaseUrl, ho.WebappBaseUrl)),
zap.String("baseDir", ho.WebappBaseDir),
zap.Strings("apps", strings.Split(ho.WebappList, ",")),
)
}()
// Auth server
app.AuthService.MountHttpRoutes(ho.BaseUrl, r)
func() {
if !ho.ApiEnabled {
app.Log.Info("JSON REST API disabled")
return
}
r.Route(options.CleanBase(ho.ApiBaseUrl), func(r chi.Router) {
var fullpathAPI = "/" + strings.TrimPrefix(options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl), "/")
app.Log.Info(
"JSON REST API enabled",
zap.String("baseUrl", fullpathAPI),
)
r.Route("/system", systemRest.MountRoutes())
r.Route("/automation", automationRest.MountRoutes())
r.Route("/compose", composeRest.MountRoutes())
r.Route("/websocket", app.WsServer.MountRoutes)
if app.Opt.Discovery.Enabled {
r.Route("/discovery", discoveryRest.MountRoutes(app.Opt.Discovery))
}
if app.Opt.Federation.Enabled {
r.Route("/federation", federationRest.MountRoutes(app.Opt.Limit))
}
var fullpathDocs = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "docs")
app.Log.Info(
"API docs enabled",
zap.String("baseUrl", fullpathDocs),
)
r.Handle("/docs", http.RedirectHandler(fullpathDocs+"/", http.StatusPermanentRedirect))
r.Handle("/docs*", http.StripPrefix(fullpathDocs, http.FileServer(docs.GetFS())))
var fullpathGateway = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "gateway")
r.Handle("/gateway*", http.StripPrefix(fullpathGateway, app.ApigwService))
})
}()
func() {
if !app.Opt.SCIM.Enabled {
return
}
if app.Opt.SCIM.Secret == "" {
app.Log.
Error("SCIM secret empty")
}
var (
baseUrl = app.Opt.SCIM.BaseURL
extIdValidation *regexp.Regexp
err error
)
if len(app.Opt.SCIM.ExternalIdValidation) > 0 {
extIdValidation, err = regexp.Compile(app.Opt.SCIM.ExternalIdValidation)
}
if err != nil {
app.Log.Error("failed to compile SCIM external ID validation", zap.Error(err))
return
}
app.Log.Debug(
"SCIM enabled",
zap.String("baseUrl", path.Join(app.Opt.HTTPServer.BaseUrl, baseUrl)),
logger.Mask("secret", app.Opt.SCIM.Secret),
)
r.Route(baseUrl, func(r chi.Router) {
if !app.Opt.Environment.IsDevelopment() {
r.Use(scim.Guard(app.Opt.SCIM))
}
scim.Routes(r, scim.Config{
ExternalIdAsPrimary: app.Opt.SCIM.ExternalIdAsPrimary,
ExternalIdValidator: extIdValidation,
})
})
}()
func() {
r.Handle("/.well-known/openid-configuration", app.AuthService.WellKnownOpenIDConfiguration())
// clients discover the document under the issuer URL (AUTH_BASE_URL) that ends with /auth
r.Handle("/auth/.well-known/openid-configuration", app.AuthService.WellKnownOpenIDConfiguration())
}()
}

9
server/app/store.go Normal file
View File

@@ -0,0 +1,9 @@
package app
// Registers all supported store backends
import (
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/mssql"
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/mysql"
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/postgres"
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
)

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAw3+wJCWIjrhAwz9AaCZ2gRgWjs/CpKqZWRWFXmMDULmamt8q
k3x96WcXJU9+rELeHAmOkC6iOVFBaCgImj712kUmslc5TEc0ni0MdLp6hL26PEcY
fPcRKCBH8DlHWJL9/M/DXBnixDLZJJqAPpWXfmiGgyInzAzkEC7lVyfVDYDh3bd1
1t16fo/oQueko9VfDWFIAShPFfHetg1MYmig+37Xuo5HEDuKDyFVW8xEtw36lV8F
R9MtH6JeudaxPrkUZc8V97OQ5uryZFy08XcjiKGRDfiJj2X4rU9MP6cEl8nG6MVj
vFNxWEUoanN+VzAU6JLLYBrhCGFPjh5B1mw3lSsc2zd7gMGNXdmPJKQKtT5X5b0b
mSuRH/LtCKmtznbH0mengTeuda7rHPh19hbT6SDv5JPZ2XWz7QmfeNvbfh1su1S3
ILBScru5wgZu2kCpfsqEtFuXxZKOqEvin2homGuarsyebLMVX86vvmikoIlkICDT
2idoOZ6ecvMSXnkDYWyRy6TzMx/2OoewiJH2raeMDOiZ2xtiBxnz3mpG6oUIHOQX
wXL1ASMyA1XhCg+qc2rfs+n8tOcgpHBQPNLMhMxkj48OaI3YYHGtUIBdHqMW/bxs
QNWBWIorVj2WOcbji7LawXo3+cEm2XewM5C1asfOx/rP2SY1JoZ2eNwkMFcCAwEA
AQKCAgB/FUABH0z3yZp/0Vwi1+3s2SXAzBlxRVzN5gl+Y8rB7QUta6iXmzOWR+dp
35UukYEkpKnB3C6kJ8fm1y9QZWEX9B/FiqG6jgcMU6hnMNl39IVdrbGjek/yz5kz
9WgFkff9IWmTM1iPxaYV/0Eibce+6l+WWtuX70FJq9J1p3T9hCxPHnVBqVN5dj67
Ty1dOtTBM93EstlGIKZvnZFr3yvWkIvXv6k+ShXi4/5C4DRC8plUBCLAn+wTRk9h
ashpR5KU49sZ2+Zz1YKf7wvIc3Pr22sZs9hoGRdgQc4FCqMXsJjWs4mz4GIryTkO
iek08BKHZPVDkfV6pxc6AT03D73I3BsZY+Rk5bCi/QbTa4fo3fYP49InjDOlKna7
8slNF3yNBgCfk5GvEeOyPnTJ2lAEGgy/0uXMpdnUTyRB0e7IilDxOnfwmAJ6+Mhv
bQ9HZyp7Pz1GlH2eN2H7/OvFIZoZGBHVUJPzQtBpJMeRHBf9rd6sT9MMAcAXS5XO
WD0ceGWUA8ySPv9PgQkXq146yR75lsbEUlG3K69wPD+hjif5DW7ual7KuJ709w1m
Atzmo7LMvnffLBslVxtmLIFlVldNVPVnROjD6F5ewO/IvbpFug5M2yLw0cYH3xnB
qVYbK16TfA4LWQo8Ign/XfLRjqNnA488EhwejAwQVZAvUYyOkQKCAQEAzhmjRCbx
cGMSM2gs317Fped1NDZjUTB8WgcEGqjy58Rn6BbVcAwUNAFfddOy+WtPAHUsSRMM
awhkIBkJPyHD4VX3kxisJa2+qSjuWDlZH1hsCkb1XdwaiGignwsNGOmba/6izYJC
OBnieq3hl8/6/AZXdpSdDlpa6iLyHKF6R6S6T0jXWii2jKlCM+tMaMYBoE4mkUkh
8GTPE1ilscT2Lc5/YuzLSUdlOCKDsrFVx713H55pI8jISZIsuE72AmdmZfGz2YpF
PunbsnpM0vl3V8tiAOip7z99SUlTznRdaxbUaaE6LEEz6zFOtjOtSI9HZKdXA7KT
NX0uAmklz3C3xQKCAQEA8tT2pkt7ONrsUpQjfQXm4g6l9Ne6zvP5YfOrA4pCtMz+
lVs0WomSQ76tnwz7aFfMUIXo3B13judTEJS4SQPXScTNnWOYfQUxFyXymCnR5uKD
5GB3v6I+z0ISf4XY69uPdqT309Y7oDRri8qjuCBpL4CA3X8d0lm16sk/4QIdf6f2
IDnVx86eLR5DVIQkt56TBRahMZoY+xGgUl62Bh/FOIrrXN2U7Uzy9D2Ci1YzmSwX
K8y3sgjLUs+QCD29v3Ud8SJrQLLrgD2jxSIPAFtgUASyBIONvL3BHnoYbtI5nG9m
wpyM2bbaVlMt85PFTwFvyxkeJtZo7KD3MjFfrq7tawKCAQBxtZdsfIfs19ws3KcV
PjaSCBeUDQXSv4t8KC7NfrU1xhkF5cMwpJlo7/D5EynFjrzxpbQfRREu5FxJkUaW
vExUQJftYkloKGnu7pRBvLI9Jw3Exc2OUWeaJw+nb/Nz8T884Cp2dA2Q4kcgJFs4
5Ri2f9E1rVzRlGxgjZNC1JygdnQMVkXqilfdV20dNA7eJ5CyHywMMGs3NIdDRz9z
yxV/xFRoAflOnsNrqy86IbkQEKpumXmsspQ+cxWVQotcXCwUxrvRpAX8Zg4+dFd6
dwSvPZj+o19OjprTGk/QskbwVJIxDTEJRZgdhQDGadCLHfHnyKOreJTbiAsgbV7d
CnV5AoIBAHXOH0KZx9PTUWbWe0kB/fqZRKW9quEPt2Jvxf1Xq/juomtu7b0mlJDN
Bons0GLUhUhjTo7KtN/dFY0ruSOi+2TMVzwNv0KXrDMgs8piL3SlW3sF2dJcMcB2
u2amVpBF2hmi+qAIhLC5/Zq52idPgfgLjXjnXF5aK0kWDSlpz0nJADyuEip28IHO
9rcfzHhQM976FYVszsWuzWpojEH4U8OUkY5h6Qgelpcq6BQU479hhnQ3Qr7aSVmJ
XMTCvzUM6lO7cPDYXCvAu5y9Yy23JshrHrnV4IM6Q2A3t3a8AjsESIQNUr+kV9Qd
UmOiwswLVGLhcqFAz3JMvN/a2CJ0trUCggEBALFfeFaypCdGFRZeRTbh/Xr2iK2a
BCrws428sheuEVXUm88xV6HtQ0Bxk4gYKZ6RggBpCyRaO/27UOlkj7vZJuEE+lkp
VcJLUuU83XQiF1sHXXqE55wZm50vLHicroR727uR1iCiqpJyg4A+hkJAq5D36oOs
rwKDzRJXDyzEqpGNXo19kVg0eonujROqDBvZSYWwx43C859P0HoFCpRcmW2NdDcQ
POlHwuYQHnQTFubb04RCQeQHmnbPMpUJCiwbBSeuY6assnfUmFs6zRzO4h2h2AU1
nJZiTlrDoc1e7zmwJMNBdhaXt0YoJGw8uts/FiXqz/lALwVQmDLIKPIo4mE=
-----END RSA PRIVATE KEY-----

93
server/assets/embed.go Normal file
View File

@@ -0,0 +1,93 @@
package assets
import (
"embed"
"fmt"
"io/fs"
"os"
"path"
"go.uber.org/zap"
)
var (
//go:embed src/*
ff embed.FS
)
// Files return files from assets path or falls back to embedded files
//
// @todo try to find a way to merge this with auth/assets
func Files(log *zap.Logger, aPath string) (files fs.FS) {
var err error
if len(aPath) > 0 {
if files, err = fromPath(aPath); err != nil {
// log warning but fallback to embedded assets
log.Warn(
fmt.Sprintf("failed to use custom assets path (HTTP_SERVER_ASSETS_PATH=%s)", aPath),
zap.Error(err),
)
}
}
if files == nil {
aPath = "embedded"
files, err = fs.Sub(ff, "src")
if err != nil {
// something is seriously wrong, we might as well panic
panic(err)
}
}
return
}
func fromPath(path string) (assets fs.FS, err error) {
// at least icon file should exist in the custom asset path
// otherwise we default to embedded files
const check = "icon.svg"
var (
fi os.FileInfo
)
if fi, err = os.Stat(path); err != nil {
return
}
if !fi.IsDir() {
return nil, fmt.Errorf("expecting directory")
}
assets = os.DirFS(path)
if _, err = assets.Open(check); err != nil {
return nil, err
}
return
}
func DirEntries(dir string) (fileNames, subDirs []string, err error) {
dirEntries, err := fs.ReadDir(ff, path.Join("src", dir))
if err != nil {
return nil, nil, err
}
for _, dirEntry := range dirEntries {
fileInfo, err := dirEntry.Info()
if err != nil {
return nil, nil, err
}
// if the entry is a directory skip it
if fileInfo.IsDir() {
subDirs = append(subDirs, dirEntry.Name())
continue
}
fileNames = append(fileNames, dirEntry.Name())
}
return fileNames, subDirs, err
}

View File

@@ -0,0 +1,63 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<link href="/auth/assets/public/style.css?2022-02-21T16%3a52%3a49%2b01%3a00" rel="stylesheet">
<title>Page not found | Corteza</title>
<style>
body {
color: white;
font-family: 'Poppins', sans-serif;
text-align: center;
display: grid;
grid-template-rows: auto 150px;
height: 100vh;
width: 100vw;
}
main {
align-self: center;
}
main h1 {
font-size: 2500%;
padding: 0;
margin: 0;
margin-bottom: -50px;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
}
main p {
font-size: 200%;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
font-weight: bold;
}
</style>
</head>
<body style="background: url(/assets/release-background.jpg) no-repeat top;background-size: cover;background-attachment: fixed;">
<main>
<div>
<h1>404</h1>
<p>
It looks like the page you're looking for does not exist.
</p>
<!-- links -->
</div>
</main>
<footer class="p-5 text-white">
Access source code on<a href="https://github.com/cortezaproject/" target="_blank" class="text-white ml-1">GitHub</a>
</footer>
</body>
</html>

View File

@@ -0,0 +1,64 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<link href="/auth/assets/public/style.css?2022-02-21T16%3a52%3a49%2b01%3a00" rel="stylesheet">
<title>Corteza API</title>
<style>
body {
color: white;
font-family: 'Poppins', sans-serif;
text-align: center;
display: grid;
grid-template-rows: auto 150px;
height: 100vh;
width: 100vw;
}
main {
align-self: center;
}
main h1 {
font-size: 2500%;
padding: 0;
margin: 0;
margin-bottom: -50px;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
}
main p {
font-size: 200%;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
font-weight: bold;
}
</style>
</head>
<body style="background: url(/assets/release-background.jpg) no-repeat top;background-size: cover;background-attachment: fixed;">
<main>
<div>
<h1>HEY</h1>
<p>
How are you? Thank you for stopping by,
<br />here is what you can do:
</p>
<!-- links -->
</div>
</main>
<footer class="p-5 text-white">
Access source code on<a href="https://github.com/cortezaproject/" target="_blank" class="text-white ml-1">GitHub</a>
</footer>
</body>
</html>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.0075 -0.0058 36.9075 38.5121" width="32px" height="32px">
<defs>
<style type="text/css">
.st0{fill:#95B4AF;}
</style>
</defs>
<path class="st0" d="M2.5,9.4c1.6-2.9,4-5.3,6.9-6.9c3-1.7,6.3-2.5,9.8-2.5c3.9-0.1,7.8,1.1,11.1,3.2c3.2,2.2,5.5,5.3,6.6,9h-8.7 c-0.8-1.7-2-3.1-3.6-4.1c-1.7-0.9-3.5-1.4-5.4-1.3c-2.1,0-4.1,0.5-5.9,1.5c-1.7,1-3.1,2.6-4,4.4c-1,2.1-1.5,4.3-1.4,6.6 c0,2.3,0.4,4.5,1.4,6.6c0.9,1.8,2.3,3.3,4,4.4c1.8,1,3.8,1.6,5.9,1.5c1.9,0,3.8-0.4,5.4-1.4c1.6-1,2.9-2.4,3.6-4.1h8.7 c-1.1,3.7-3.5,6.8-6.6,9c-3.3,2.2-7.2,3.3-11.1,3.2c-3.4,0-6.8-0.8-9.8-2.5c-2.9-1.6-5.2-4-6.9-6.8c-1.7-3-2.6-6.5-2.5-10 C-0.1,15.9,0.8,12.4,2.5,9.4z" id="object-0" style="fill: rgb(255, 150, 97);"/>
</svg>

After

Width:  |  Height:  |  Size: 792 B

BIN
server/assets/src/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 241.45 38.63"><defs><style>.cls-1{fill:#FF9661;}</style></defs><path class="cls-1" d="M2.51,9.38A18,18,0,0,1,9.37,2.51,19.49,19.49,0,0,1,19.13.05,19.15,19.15,0,0,1,30.21,3.3a17,17,0,0,1,6.64,9h-8.7A9,9,0,0,0,24.5,8.21a10.67,10.67,0,0,0-5.43-1.35A11.24,11.24,0,0,0,13.21,8.4a10.6,10.6,0,0,0-4,4.35,14.41,14.41,0,0,0-1.43,6.59,14.44,14.44,0,0,0,1.43,6.57,10.55,10.55,0,0,0,4,4.38,11.24,11.24,0,0,0,5.86,1.54,10.49,10.49,0,0,0,5.43-1.38,9.16,9.16,0,0,0,3.65-4.08h8.7a16.92,16.92,0,0,1-6.62,9,19.27,19.27,0,0,1-11.1,3.21,19.49,19.49,0,0,1-9.76-2.46,18,18,0,0,1-6.86-6.83A19.69,19.69,0,0,1,0,19.34,19.76,19.76,0,0,1,2.51,9.38Z"/><path class="cls-1" d="M50.43,36.15a18.53,18.53,0,0,1-7-6.89,19.28,19.28,0,0,1-2.59-10A19.13,19.13,0,0,1,43.4,9.38a18.46,18.46,0,0,1,7-6.89,20.29,20.29,0,0,1,19.48,0,18.37,18.37,0,0,1,7,6.89,19.31,19.31,0,0,1,2.56,9.91,19.45,19.45,0,0,1-2.56,10,18.38,18.38,0,0,1-7,6.89,20.31,20.31,0,0,1-19.45,0Zm15.72-5.81a10.53,10.53,0,0,0,4-4.4,14.38,14.38,0,0,0,1.46-6.65,14.2,14.2,0,0,0-1.46-6.62,10.4,10.4,0,0,0-4-4.35,12.72,12.72,0,0,0-12,0,10.34,10.34,0,0,0-4.08,4.35,14.32,14.32,0,0,0-1.46,6.62,14.51,14.51,0,0,0,1.46,6.65,10.47,10.47,0,0,0,4.08,4.4,12.5,12.5,0,0,0,12,0Z"/><path class="cls-1" d="M103.92,38.26,95.6,23.56H92v14.7H84.47V.54H98.62a16.49,16.49,0,0,1,7.46,1.54,10.8,10.8,0,0,1,4.62,4.16,11.32,11.32,0,0,1,1.54,5.86,11.23,11.23,0,0,1-2.16,6.73A11,11,0,0,1,103.65,23l9,15.3ZM92,17.89h6.32A6.33,6.33,0,0,0,103,16.4a5.52,5.52,0,0,0,1.51-4.13,5.29,5.29,0,0,0-1.51-4,6.5,6.5,0,0,0-4.6-1.43H92Z"/><path class="cls-1" d="M142.88.54V6.65h-10V38.26h-7.57V6.65H115.21V.54Z"/><path class="cls-1" d="M155.32,6.65V16.1H168v6h-12.7v10h14.32v6.16H147.75V.49h21.89V6.65Z"/><path class="cls-1" d="M185.42,31.88h17v6.38H176.66V32.42l16.86-25.5H176.66V.54h25.72V6.38Z"/><path class="cls-1" d="M231,31.07H216l-2.49,7.19h-7.94L219.08.49h8.81l13.56,37.77h-8ZM228.91,25,223.46,9.24,218,25Z"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,52 @@
//
// Base styles
//
.alert {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
border: $alert-border-width solid transparent;
@include border-radius($alert-border-radius);
}
// Headings for larger alerts
.alert-heading {
// Specified to prevent conflicts of changing $headings-color
color: inherit;
}
// Provide class for links that match alerts
.alert-link {
font-weight: $alert-link-font-weight;
}
// Dismissible alerts
//
// Expand the right padding and account for the close button's positioning.
.alert-dismissible {
padding-right: $close-font-size + $alert-padding-x * 2;
// Adjust close link position
.close {
position: absolute;
top: 0;
right: 0;
z-index: 2;
padding: $alert-padding-y $alert-padding-x;
color: inherit;
}
}
// Alternate styles
//
// Generate contextual modifier classes for colorizing the alert.
@each $color, $value in $theme-colors {
.alert-#{$color} {
@include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level));
}
}

View File

@@ -0,0 +1,54 @@
// Base class
//
// Requires one of the contextual, color modifier classes for `color` and
// `background-color`.
.badge {
display: inline-block;
padding: $badge-padding-y $badge-padding-x;
@include font-size($badge-font-size);
font-weight: $badge-font-weight;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
@include border-radius($badge-border-radius);
@include transition($badge-transition);
@at-root a#{&} {
@include hover-focus() {
text-decoration: none;
}
}
// Empty badges collapse automatically
&:empty {
display: none;
}
}
// Quick fix for badges in buttons
.btn .badge {
position: relative;
top: -1px;
}
// Pill badges
//
// Make them extra rounded with a modifier to replace v3's badges.
.badge-pill {
padding-right: $badge-pill-padding-x;
padding-left: $badge-pill-padding-x;
@include border-radius($badge-pill-border-radius);
}
// Colors
//
// Contextual variations (linked badges get darker on :hover).
@each $color, $value in $theme-colors {
.badge-#{$color} {
@include badge-variant($value);
}
}

View File

@@ -0,0 +1,44 @@
.breadcrumb {
display: flex;
flex-wrap: wrap;
padding: $breadcrumb-padding-y $breadcrumb-padding-x;
margin-bottom: $breadcrumb-margin-bottom;
@include font-size($breadcrumb-font-size);
list-style: none;
background-color: $breadcrumb-bg;
@include border-radius($breadcrumb-border-radius);
}
.breadcrumb-item {
display: flex;
// The separator between breadcrumbs (by default, a forward-slash: "/")
+ .breadcrumb-item {
padding-left: $breadcrumb-item-padding;
&::before {
display: inline-block; // Suppress underlining of the separator in modern browsers
padding-right: $breadcrumb-item-padding;
color: $breadcrumb-divider-color;
content: escape-svg($breadcrumb-divider);
}
}
// IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built
// without `<ul>`s. The `::before` pseudo-element generates an element
// *within* the .breadcrumb-item and thereby inherits the `text-decoration`.
//
// To trick IE into suppressing the underline, we give the pseudo-element an
// underline and then immediately remove it.
+ .breadcrumb-item:hover::before {
text-decoration: underline;
}
// stylelint-disable-next-line no-duplicate-selectors
+ .breadcrumb-item:hover::before {
text-decoration: none;
}
&.active {
color: $breadcrumb-active-color;
}
}

View File

@@ -0,0 +1,163 @@
// stylelint-disable selector-no-qualifying-type
// Make the div behave like a button
.btn-group,
.btn-group-vertical {
position: relative;
display: inline-flex;
vertical-align: middle; // match .btn alignment given font-size hack above
> .btn {
position: relative;
flex: 1 1 auto;
// Bring the hover, focused, and "active" buttons to the front to overlay
// the borders properly
@include hover() {
z-index: 1;
}
&:focus,
&:active,
&.active {
z-index: 1;
}
}
}
// Optional: Group multiple button groups together for a toolbar
.btn-toolbar {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.input-group {
width: auto;
}
}
.btn-group {
// Prevent double borders when buttons are next to each other
> .btn:not(:first-child),
> .btn-group:not(:first-child) {
margin-left: -$btn-border-width;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-right-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-left-radius(0);
}
}
// Sizing
//
// Remix the default button sizing classes into new ones for easier manipulation.
.btn-group-sm > .btn { @extend .btn-sm; }
.btn-group-lg > .btn { @extend .btn-lg; }
//
// Split button dropdowns
//
.dropdown-toggle-split {
padding-right: $btn-padding-x * .75;
padding-left: $btn-padding-x * .75;
&::after,
.dropup &::after,
.dropright &::after {
margin-left: 0;
}
.dropleft &::before {
margin-right: 0;
}
}
.btn-sm + .dropdown-toggle-split {
padding-right: $btn-padding-x-sm * .75;
padding-left: $btn-padding-x-sm * .75;
}
.btn-lg + .dropdown-toggle-split {
padding-right: $btn-padding-x-lg * .75;
padding-left: $btn-padding-x-lg * .75;
}
// The clickable button for toggling the menu
// Set the same inset shadow as the :active state
.btn-group.show .dropdown-toggle {
@include box-shadow($btn-active-box-shadow);
// Show no shadow for `.btn-link` since it has no other button styles.
&.btn-link {
@include box-shadow(none);
}
}
//
// Vertical button groups
//
.btn-group-vertical {
flex-direction: column;
align-items: flex-start;
justify-content: center;
> .btn,
> .btn-group {
width: 100%;
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) {
margin-top: -$btn-border-width;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-bottom-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-top-radius(0);
}
}
// Checkbox and radio options
//
// In order to support the browser's form validation feedback, powered by the
// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
// `display: none;` or `visibility: hidden;` as that also hides the popover.
// Simply visually hiding the inputs via `opacity` would leave them clickable in
// certain cases which is prevented by using `clip` and `pointer-events`.
// This way, we ensure a DOM element is visible to position the popover from.
//
// See https://github.com/twbs/bootstrap/pull/12794 and
// https://github.com/twbs/bootstrap/pull/14559 for more information.
.btn-group-toggle {
> .btn,
> .btn-group > .btn {
margin-bottom: 0; // Override default `<label>` value
input[type="radio"],
input[type="checkbox"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
}
}

View File

@@ -0,0 +1,142 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.btn {
display: inline-block;
font-family: $btn-font-family;
font-weight: $btn-font-weight;
color: $body-color;
text-align: center;
text-decoration: if($link-decoration == none, null, none);
white-space: $btn-white-space;
vertical-align: middle;
user-select: none;
background-color: transparent;
border: $btn-border-width solid transparent;
@include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-line-height, $btn-border-radius);
@include transition($btn-transition);
@include hover() {
color: $body-color;
text-decoration: none;
}
&:focus,
&.focus {
outline: 0;
box-shadow: $btn-focus-box-shadow;
}
// Disabled comes first so active can properly restyle
&.disabled,
&:disabled {
opacity: $btn-disabled-opacity;
@include box-shadow(none);
}
&:not(:disabled):not(.disabled) {
cursor: if($enable-pointer-cursor-for-buttons, pointer, null);
&:active,
&.active {
@include box-shadow($btn-active-box-shadow);
&:focus {
@include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);
}
}
}
}
// Future-proof disabling of clicks on `<a>` elements
a.btn.disabled,
fieldset:disabled a.btn {
pointer-events: none;
}
//
// Alternate buttons
//
@each $color, $value in $theme-colors {
.btn-#{$color} {
@include button-variant($value, $value);
}
}
@each $color, $value in $theme-colors {
.btn-outline-#{$color} {
@include button-outline-variant($value);
}
}
//
// Link buttons
//
// Make a button look and behave like a link
.btn-link {
font-weight: $font-weight-normal;
color: $link-color;
text-decoration: $link-decoration;
@include hover() {
color: $link-hover-color;
text-decoration: $link-hover-decoration;
}
&:focus,
&.focus {
text-decoration: $link-hover-decoration;
}
&:disabled,
&.disabled {
color: $btn-link-disabled-color;
pointer-events: none;
}
// No need for an active state here
}
//
// Button Sizes
//
.btn-lg {
@include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-line-height-lg, $btn-border-radius-lg);
}
.btn-sm {
@include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-line-height-sm, $btn-border-radius-sm);
}
//
// Block button
//
.btn-block {
display: block;
width: 100%;
// Vertically space out multiple block buttons
+ .btn-block {
margin-top: $btn-block-spacing-y;
}
}
// Specificity overrides
input[type="submit"],
input[type="reset"],
input[type="button"] {
&.btn-block {
width: 100%;
}
}

View File

@@ -0,0 +1,197 @@
// Notes on the classes:
//
// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)
// even when their scroll action started on a carousel, but for compatibility (with Firefox)
// we're preventing all actions instead
// 2. The .carousel-item-left and .carousel-item-right is used to indicate where
// the active slide is heading.
// 3. .active.carousel-item is the current slide.
// 4. .active.carousel-item-left and .active.carousel-item-right is the current
// slide in its in-transition state. Only one of these occurs at a time.
// 5. .carousel-item-next.carousel-item-left and .carousel-item-prev.carousel-item-right
// is the upcoming slide in transition.
.carousel {
position: relative;
}
.carousel.pointer-event {
touch-action: pan-y;
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden;
@include clearfix();
}
.carousel-item {
position: relative;
display: none;
float: left;
width: 100%;
margin-right: -100%;
backface-visibility: hidden;
@include transition($carousel-transition);
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: block;
}
.carousel-item-next:not(.carousel-item-left),
.active.carousel-item-right {
transform: translateX(100%);
}
.carousel-item-prev:not(.carousel-item-right),
.active.carousel-item-left {
transform: translateX(-100%);
}
//
// Alternate transitions
//
.carousel-fade {
.carousel-item {
opacity: 0;
transition-property: opacity;
transform: none;
}
.carousel-item.active,
.carousel-item-next.carousel-item-left,
.carousel-item-prev.carousel-item-right {
z-index: 1;
opacity: 1;
}
.active.carousel-item-left,
.active.carousel-item-right {
z-index: 0;
opacity: 0;
@include transition(opacity 0s $carousel-transition-duration);
}
}
//
// Left/right controls for nav
//
.carousel-control-prev,
.carousel-control-next {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
// Use flex for alignment (1-3)
display: flex; // 1. allow flex styles
align-items: center; // 2. vertically center contents
justify-content: center; // 3. horizontally center contents
width: $carousel-control-width;
color: $carousel-control-color;
text-align: center;
opacity: $carousel-control-opacity;
@include transition($carousel-control-transition);
// Hover/focus state
@include hover-focus() {
color: $carousel-control-color;
text-decoration: none;
outline: 0;
opacity: $carousel-control-hover-opacity;
}
}
.carousel-control-prev {
left: 0;
@if $enable-gradients {
background-image: linear-gradient(90deg, rgba($black, .25), rgba($black, .001));
}
}
.carousel-control-next {
right: 0;
@if $enable-gradients {
background-image: linear-gradient(270deg, rgba($black, .25), rgba($black, .001));
}
}
// Icons for within
.carousel-control-prev-icon,
.carousel-control-next-icon {
display: inline-block;
width: $carousel-control-icon-width;
height: $carousel-control-icon-width;
background: no-repeat 50% / 100% 100%;
}
.carousel-control-prev-icon {
background-image: escape-svg($carousel-control-prev-icon-bg);
}
.carousel-control-next-icon {
background-image: escape-svg($carousel-control-next-icon-bg);
}
// Optional indicator pips
//
// Add an ordered list with the following class and add a list item for each
// slide your carousel holds.
.carousel-indicators {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 15;
display: flex;
justify-content: center;
padding-left: 0; // override <ol> default
// Use the .carousel-control's width as margin so we don't overlay those
margin-right: $carousel-control-width;
margin-left: $carousel-control-width;
list-style: none;
li {
box-sizing: content-box;
flex: 0 1 auto;
width: $carousel-indicator-width;
height: $carousel-indicator-height;
margin-right: $carousel-indicator-spacer;
margin-left: $carousel-indicator-spacer;
text-indent: -999px;
cursor: pointer;
background-color: $carousel-indicator-active-bg;
background-clip: padding-box;
// Use transparent borders to increase the hit area by 10px on top and bottom.
border-top: $carousel-indicator-hit-area-height solid transparent;
border-bottom: $carousel-indicator-hit-area-height solid transparent;
opacity: .5;
@include transition($carousel-indicator-transition);
}
.active {
opacity: 1;
}
}
// Optional captions
//
//
.carousel-caption {
position: absolute;
right: (100% - $carousel-caption-width) / 2;
bottom: 20px;
left: (100% - $carousel-caption-width) / 2;
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: $carousel-caption-color;
text-align: center;
}

View File

@@ -0,0 +1,40 @@
.close {
float: right;
@include font-size($close-font-size);
font-weight: $close-font-weight;
line-height: 1;
color: $close-color;
text-shadow: $close-text-shadow;
opacity: .5;
// Override <a>'s hover style
@include hover() {
color: $close-color;
text-decoration: none;
}
&:not(:disabled):not(.disabled) {
@include hover-focus() {
opacity: .75;
}
}
}
// Additional properties for button version
// iOS requires the button element instead of an anchor tag.
// If you want the anchor version, it requires `href="#"`.
// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
// stylelint-disable-next-line selector-no-qualifying-type
button.close {
padding: 0;
background-color: transparent;
border: 0;
}
// Future-proof disabling of clicks on `<a>` elements
// stylelint-disable-next-line selector-no-qualifying-type
a.close.disabled {
pointer-events: none;
}

View File

@@ -0,0 +1,48 @@
// Inline code
code {
@include font-size($code-font-size);
color: $code-color;
word-wrap: break-word;
// Streamline the style when inside anchors to avoid broken underline and more
a > & {
color: inherit;
}
}
// User input typically entered via keyboard
kbd {
padding: $kbd-padding-y $kbd-padding-x;
@include font-size($kbd-font-size);
color: $kbd-color;
background-color: $kbd-bg;
@include border-radius($border-radius-sm);
@include box-shadow($kbd-box-shadow);
kbd {
padding: 0;
@include font-size(100%);
font-weight: $nested-kbd-font-weight;
@include box-shadow(none);
}
}
// Blocks of code
pre {
display: block;
@include font-size($code-font-size);
color: $pre-color;
// Account for some code outputs that place code tags in pre tags
code {
@include font-size(inherit);
color: inherit;
word-break: normal;
}
}
// Enable scrollable blocks of code
.pre-scrollable {
max-height: $pre-scrollable-max-height;
overflow-y: scroll;
}

View File

@@ -0,0 +1,527 @@
// Embedded icons from Open Iconic.
// Released under MIT and copyright 2014 Waybury.
// https://useiconic.com/open
// Checkboxes and radios
//
// Base class takes care of all the key behavioral aspects.
.custom-control {
position: relative;
z-index: 1;
display: block;
min-height: $font-size-base * $line-height-base;
padding-left: $custom-control-gutter + $custom-control-indicator-size;
color-adjust: exact; // Keep themed appearance for print
}
.custom-control-inline {
display: inline-flex;
margin-right: $custom-control-spacer-x;
}
.custom-control-input {
position: absolute;
left: 0;
z-index: -1; // Put the input behind the label so it doesn't overlay text
width: $custom-control-indicator-size;
height: ($font-size-base * $line-height-base + $custom-control-indicator-size) / 2;
opacity: 0;
&:checked ~ .custom-control-label::before {
color: $custom-control-indicator-checked-color;
border-color: $custom-control-indicator-checked-border-color;
@include gradient-bg($custom-control-indicator-checked-bg);
@include box-shadow($custom-control-indicator-checked-box-shadow);
}
&:focus ~ .custom-control-label::before {
// the mixin is not used here to make sure there is feedback
@if $enable-shadows {
box-shadow: $input-box-shadow, $custom-control-indicator-focus-box-shadow;
} @else {
box-shadow: $custom-control-indicator-focus-box-shadow;
}
}
&:focus:not(:checked) ~ .custom-control-label::before {
border-color: $custom-control-indicator-focus-border-color;
}
&:not(:disabled):active ~ .custom-control-label::before {
color: $custom-control-indicator-active-color;
background-color: $custom-control-indicator-active-bg;
border-color: $custom-control-indicator-active-border-color;
@include box-shadow($custom-control-indicator-active-box-shadow);
}
// Use [disabled] and :disabled to work around https://github.com/twbs/bootstrap/issues/28247
&[disabled],
&:disabled {
~ .custom-control-label {
color: $custom-control-label-disabled-color;
&::before {
background-color: $custom-control-indicator-disabled-bg;
}
}
}
}
// Custom control indicators
//
// Build the custom controls out of pseudo-elements.
.custom-control-label {
position: relative;
margin-bottom: 0;
color: $custom-control-label-color;
vertical-align: top;
cursor: $custom-control-cursor;
// Background-color and (when enabled) gradient
&::before {
position: absolute;
top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;
left: -($custom-control-gutter + $custom-control-indicator-size);
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
pointer-events: none;
content: "";
background-color: $custom-control-indicator-bg;
border: $custom-control-indicator-border-color solid $custom-control-indicator-border-width;
@include box-shadow($custom-control-indicator-box-shadow);
}
// Foreground (icon)
&::after {
position: absolute;
top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;
left: -($custom-control-gutter + $custom-control-indicator-size);
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
content: "";
background: no-repeat 50% / #{$custom-control-indicator-bg-size};
}
}
// Checkboxes
//
// Tweak just a few things for checkboxes.
.custom-checkbox {
.custom-control-label::before {
@include border-radius($custom-checkbox-indicator-border-radius);
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-image: escape-svg($custom-checkbox-indicator-icon-checked);
}
}
.custom-control-input:indeterminate ~ .custom-control-label {
&::before {
border-color: $custom-checkbox-indicator-indeterminate-border-color;
@include gradient-bg($custom-checkbox-indicator-indeterminate-bg);
@include box-shadow($custom-checkbox-indicator-indeterminate-box-shadow);
}
&::after {
background-image: escape-svg($custom-checkbox-indicator-icon-indeterminate);
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
&:indeterminate ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
}
}
// Radios
//
// Tweak just a few things for radios.
.custom-radio {
.custom-control-label::before {
// stylelint-disable-next-line property-disallowed-list
border-radius: $custom-radio-indicator-border-radius;
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-image: escape-svg($custom-radio-indicator-icon-checked);
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
}
}
// switches
//
// Tweak a few things for switches
.custom-switch {
padding-left: $custom-switch-width + $custom-control-gutter;
.custom-control-label {
&::before {
left: -($custom-switch-width + $custom-control-gutter);
width: $custom-switch-width;
pointer-events: all;
// stylelint-disable-next-line property-disallowed-list
border-radius: $custom-switch-indicator-border-radius;
}
&::after {
top: add(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2, $custom-control-indicator-border-width * 2);
left: add(-($custom-switch-width + $custom-control-gutter), $custom-control-indicator-border-width * 2);
width: $custom-switch-indicator-size;
height: $custom-switch-indicator-size;
background-color: $custom-control-indicator-border-color;
// stylelint-disable-next-line property-disallowed-list
border-radius: $custom-switch-indicator-border-radius;
@include transition(transform .15s ease-in-out, $custom-forms-transition);
}
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-color: $custom-control-indicator-bg;
transform: translateX($custom-switch-width - $custom-control-indicator-size);
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
}
}
// Select
//
// Replaces the browser default select with a custom one, mostly pulled from
// https://primer.github.io/.
//
.custom-select {
display: inline-block;
width: 100%;
height: $custom-select-height;
padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x;
font-family: $custom-select-font-family;
@include font-size($custom-select-font-size);
font-weight: $custom-select-font-weight;
line-height: $custom-select-line-height;
color: $custom-select-color;
vertical-align: middle;
background: $custom-select-bg $custom-select-background;
border: $custom-select-border-width solid $custom-select-border-color;
@include border-radius($custom-select-border-radius, 0);
@include box-shadow($custom-select-box-shadow);
appearance: none;
&:focus {
border-color: $custom-select-focus-border-color;
outline: 0;
@if $enable-shadows {
@include box-shadow($custom-select-box-shadow, $custom-select-focus-box-shadow);
} @else {
// Avoid using mixin so we can pass custom focus shadow properly
box-shadow: $custom-select-focus-box-shadow;
}
&::-ms-value {
// For visual consistency with other platforms/browsers,
// suppress the default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
&[multiple],
&[size]:not([size="1"]) {
height: auto;
padding-right: $custom-select-padding-x;
background-image: none;
}
&:disabled {
color: $custom-select-disabled-color;
background-color: $custom-select-disabled-bg;
}
// Hides the default caret in IE11
&::-ms-expand {
display: none;
}
// Remove outline from select box in FF
&:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 $custom-select-color;
}
}
.custom-select-sm {
height: $custom-select-height-sm;
padding-top: $custom-select-padding-y-sm;
padding-bottom: $custom-select-padding-y-sm;
padding-left: $custom-select-padding-x-sm;
@include font-size($custom-select-font-size-sm);
}
.custom-select-lg {
height: $custom-select-height-lg;
padding-top: $custom-select-padding-y-lg;
padding-bottom: $custom-select-padding-y-lg;
padding-left: $custom-select-padding-x-lg;
@include font-size($custom-select-font-size-lg);
}
// File
//
// Custom file input.
.custom-file {
position: relative;
display: inline-block;
width: 100%;
height: $custom-file-height;
margin-bottom: 0;
}
.custom-file-input {
position: relative;
z-index: 2;
width: 100%;
height: $custom-file-height;
margin: 0;
opacity: 0;
&:focus ~ .custom-file-label {
border-color: $custom-file-focus-border-color;
box-shadow: $custom-file-focus-box-shadow;
}
// Use [disabled] and :disabled to work around https://github.com/twbs/bootstrap/issues/28247
&[disabled] ~ .custom-file-label,
&:disabled ~ .custom-file-label {
background-color: $custom-file-disabled-bg;
cursor: not-allowed
}
@each $lang, $value in $custom-file-text {
&:lang(#{$lang}) ~ .custom-file-label::after {
content: $value;
}
}
~ .custom-file-label[data-browse]::after {
content: attr(data-browse);
}
}
.custom-file-label {
cursor: pointer;
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 1;
height: $custom-file-height;
padding: $custom-file-padding-y $custom-file-padding-x;
font-family: $custom-file-font-family;
font-weight: $custom-file-font-weight;
line-height: $custom-file-line-height;
color: $custom-file-color;
background-color: $custom-file-bg;
border: $custom-file-border-width solid $custom-file-border-color;
@include border-radius($custom-file-border-radius);
@include box-shadow($custom-file-box-shadow);
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 3;
display: block;
height: $custom-file-height-inner;
padding: $custom-file-padding-y $custom-file-padding-x;
font-family: $btn-font-family;
line-height: $custom-file-line-height;
color: $custom-file-button-color;
content: "Browse";
@include gradient-bg($custom-file-button-bg);
border-left: inherit;
border-radius: 0;
}
}
// Range
//
// Style range inputs the same across browsers. Vendor-specific rules for pseudo
// elements cannot be mixed. As such, there are no shared styles for focus or
// active states on prefixed selectors.
.custom-range {
width: 100%;
height: add($custom-range-thumb-height, $custom-range-thumb-focus-box-shadow-width * 2);
padding: 0; // Need to reset padding
background-color: transparent;
appearance: none;
&:focus {
outline: none;
// Pseudo-elements must be split across multiple rulesets to have an effect.
// No box-shadow() mixin for focus accessibility.
&::-webkit-slider-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
&::-moz-range-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
&::-ms-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
}
&::-moz-focus-outer {
border: 0;
}
&::-webkit-slider-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
margin-top: ($custom-range-track-height - $custom-range-thumb-height) / 2; // Webkit specific
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-webkit-slider-runnable-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent; // Why?
cursor: $custom-range-track-cursor;
background-color: $custom-range-track-bg;
border-color: transparent;
@include border-radius($custom-range-track-border-radius);
@include box-shadow($custom-range-track-box-shadow);
}
&::-moz-range-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-moz-range-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent;
cursor: $custom-range-track-cursor;
background-color: $custom-range-track-bg;
border-color: transparent; // Firefox specific?
@include border-radius($custom-range-track-border-radius);
@include box-shadow($custom-range-track-box-shadow);
}
&::-ms-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
margin-top: 0; // Edge specific
margin-right: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.
margin-left: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-ms-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent;
cursor: $custom-range-track-cursor;
background-color: transparent;
border-color: transparent;
border-width: $custom-range-thumb-height / 2;
@include box-shadow($custom-range-track-box-shadow);
}
&::-ms-fill-lower {
background-color: $custom-range-track-bg;
@include border-radius($custom-range-track-border-radius);
}
&::-ms-fill-upper {
margin-right: 15px; // arbitrary?
background-color: $custom-range-track-bg;
@include border-radius($custom-range-track-border-radius);
}
&:disabled {
&::-webkit-slider-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
&::-webkit-slider-runnable-track {
cursor: default;
}
&::-moz-range-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
&::-moz-range-track {
cursor: default;
}
&::-ms-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
}
}
.custom-control-label::before,
.custom-file-label,
.custom-select {
@include transition($custom-forms-transition);
}

View File

@@ -0,0 +1,192 @@
// The dropdown wrapper (`<div>`)
.dropup,
.dropright,
.dropdown,
.dropleft {
position: relative;
}
.dropdown-toggle {
white-space: nowrap;
// Generate the caret automatically
@include caret();
}
// The dropdown menu
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: $zindex-dropdown;
display: none; // none by default, but block on "open" of the menu
float: left;
min-width: $dropdown-min-width;
padding: $dropdown-padding-y $dropdown-padding-x;
margin: $dropdown-spacer 0 0; // override default ul
@include font-size($dropdown-font-size);
color: $dropdown-color;
text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)
list-style: none;
background-color: $dropdown-bg;
background-clip: padding-box;
border: $dropdown-border-width solid $dropdown-border-color;
@include border-radius($dropdown-border-radius);
@include box-shadow($dropdown-box-shadow);
}
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.dropdown-menu#{$infix}-left {
right: auto;
left: 0;
}
.dropdown-menu#{$infix}-right {
right: 0;
left: auto;
}
}
}
// Allow for dropdowns to go bottom up (aka, dropup-menu)
// Just add .dropup after the standard .dropdown class and you're set.
.dropup {
.dropdown-menu {
top: auto;
bottom: 100%;
margin-top: 0;
margin-bottom: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(up);
}
}
.dropright {
.dropdown-menu {
top: 0;
right: auto;
left: 100%;
margin-top: 0;
margin-left: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(right);
&::after {
vertical-align: 0;
}
}
}
.dropleft {
.dropdown-menu {
top: 0;
right: 100%;
left: auto;
margin-top: 0;
margin-right: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(left);
&::before {
vertical-align: 0;
}
}
}
// When enabled Popper.js, reset basic dropdown position
// stylelint-disable-next-line no-duplicate-selectors
.dropdown-menu {
&[x-placement^="top"],
&[x-placement^="right"],
&[x-placement^="bottom"],
&[x-placement^="left"] {
right: auto;
bottom: auto;
}
}
// Dividers (basically an `<hr>`) within the dropdown
.dropdown-divider {
@include nav-divider($dropdown-divider-bg, $dropdown-divider-margin-y, true);
}
// Links, buttons, and more within the dropdown menu
//
// `<button>`-specific styles are denoted with `// For <button>s`
.dropdown-item {
display: block;
width: 100%; // For `<button>`s
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
clear: both;
font-weight: $font-weight-normal;
color: $dropdown-link-color;
text-align: inherit; // For `<button>`s
text-decoration: if($link-decoration == none, null, none);
white-space: nowrap; // prevent links from randomly breaking onto new lines
background-color: transparent; // For `<button>`s
border: 0; // For `<button>`s
// Prevent dropdown overflow if there's no padding
// See https://github.com/twbs/bootstrap/pull/27703
@if $dropdown-padding-y == 0 {
&:first-child {
@include border-top-radius($dropdown-inner-border-radius);
}
&:last-child {
@include border-bottom-radius($dropdown-inner-border-radius);
}
}
@include hover-focus() {
color: $dropdown-link-hover-color;
text-decoration: none;
@include gradient-bg($dropdown-link-hover-bg);
}
&.active,
&:active {
color: $dropdown-link-active-color;
text-decoration: none;
@include gradient-bg($dropdown-link-active-bg);
}
&.disabled,
&:disabled {
color: $dropdown-link-disabled-color;
pointer-events: none;
background-color: transparent;
// Remove CSS gradients if they're enabled
@if $enable-gradients {
background-image: none;
}
}
}
.dropdown-menu.show {
display: block;
}
// Dropdown section headers
.dropdown-header {
display: block;
padding: $dropdown-header-padding;
margin-bottom: 0; // for use with heading elements
@include font-size($font-size-sm);
color: $dropdown-header-color;
white-space: nowrap; // as with > li > a
}
// Dropdown text
.dropdown-item-text {
display: block;
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
color: $dropdown-link-color;
}

View File

@@ -0,0 +1,347 @@
// stylelint-disable selector-no-qualifying-type
//
// Textual form controls
//
.form-control {
display: block;
width: 100%;
height: $input-height;
padding: $input-padding-y $input-padding-x;
font-family: $input-font-family;
@include font-size($input-font-size);
font-weight: $input-font-weight;
line-height: $input-line-height;
color: $input-color;
background-color: $input-bg;
background-clip: padding-box;
border: $input-border-width solid $input-border-color;
// Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS.
@include border-radius($input-border-radius, 0);
@include box-shadow($input-box-shadow);
@include transition($input-transition);
// Unstyle the caret on `<select>`s in IE10+.
&::-ms-expand {
background-color: transparent;
border: 0;
}
// Remove select outline from select box in FF
&:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 $input-color;
}
// Customize the `:focus` state to imitate native WebKit styles.
@include form-control-focus($ignore-warning: true);
// Placeholder
&::placeholder {
color: $input-placeholder-color;
// Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.
opacity: 1;
}
// Disabled and read-only inputs
//
// HTML5 says that controls under a fieldset > legend:first-child won't be
// disabled if the fieldset is disabled. Due to implementation difficulty, we
// don't honor that edge case; we style them as disabled anyway.
&:disabled,
&[readonly] {
background-color: $input-disabled-bg;
// iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.
opacity: 1;
}
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
&.form-control {
appearance: none; // Fix appearance for date inputs in Safari
}
}
select.form-control {
&:focus::-ms-value {
// Suppress the nested default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge, as it looks bad and cannot be made to
// match the appearance of the native widget.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
// Make file inputs better match text inputs by forcing them to new lines.
.form-control-file,
.form-control-range {
display: block;
width: 100%;
}
//
// Labels
//
// For use with horizontal and inline forms, when you need the label (or legend)
// text to align with the form controls.
.col-form-label {
padding-top: add($input-padding-y, $input-border-width);
padding-bottom: add($input-padding-y, $input-border-width);
margin-bottom: 0; // Override the `<label>/<legend>` default
@include font-size(inherit); // Override the `<legend>` default
line-height: $input-line-height;
}
.col-form-label-lg {
padding-top: add($input-padding-y-lg, $input-border-width);
padding-bottom: add($input-padding-y-lg, $input-border-width);
@include font-size($input-font-size-lg);
line-height: $input-line-height-lg;
}
.col-form-label-sm {
padding-top: add($input-padding-y-sm, $input-border-width);
padding-bottom: add($input-padding-y-sm, $input-border-width);
@include font-size($input-font-size-sm);
line-height: $input-line-height-sm;
}
// Readonly controls as plain text
//
// Apply class to a readonly input to make it appear like regular plain
// text (without any border, background color, focus indicator)
.form-control-plaintext {
display: block;
width: 100%;
padding: $input-padding-y 0;
margin-bottom: 0; // match inputs if this class comes on inputs with default margins
@include font-size($input-font-size);
line-height: $input-line-height;
color: $input-plaintext-color;
background-color: transparent;
border: solid transparent;
border-width: $input-border-width 0;
&.form-control-sm,
&.form-control-lg {
padding-right: 0;
padding-left: 0;
}
}
// Form control sizing
//
// Build on `.form-control` with modifier classes to decrease or increase the
// height and font-size of form controls.
//
// Repeated in `_input_group.scss` to avoid Sass extend issues.
.form-control-sm {
height: $input-height-sm;
padding: $input-padding-y-sm $input-padding-x-sm;
@include font-size($input-font-size-sm);
line-height: $input-line-height-sm;
@include border-radius($input-border-radius-sm);
}
.form-control-lg {
height: $input-height-lg;
padding: $input-padding-y-lg $input-padding-x-lg;
@include font-size($input-font-size-lg);
line-height: $input-line-height-lg;
@include border-radius($input-border-radius-lg);
}
// stylelint-disable-next-line no-duplicate-selectors
select.form-control {
&[size],
&[multiple] {
height: auto;
}
}
textarea.form-control {
height: auto;
}
// Form groups
//
// Designed to help with the organization and spacing of vertical forms. For
// horizontal forms, use the predefined grid classes.
.form-group {
margin-bottom: $form-group-margin-bottom;
}
.form-text {
display: block;
margin-top: $form-text-margin-top;
}
// Form grid
//
// Special replacement for our grid system's `.row` for tighter form layouts.
.form-row {
display: flex;
flex-wrap: wrap;
margin-right: -$form-grid-gutter-width / 2;
margin-left: -$form-grid-gutter-width / 2;
> .col,
> [class*="col-"] {
padding-right: $form-grid-gutter-width / 2;
padding-left: $form-grid-gutter-width / 2;
}
}
// Checkboxes and radios
//
// Indent the labels to position radios/checkboxes as hanging controls.
.form-check {
position: relative;
display: block;
padding-left: $form-check-input-gutter;
}
.form-check-input {
position: absolute;
margin-top: $form-check-input-margin-y;
margin-left: -$form-check-input-gutter;
// Use [disabled] and :disabled for workaround https://github.com/twbs/bootstrap/issues/28247
&[disabled] ~ .form-check-label,
&:disabled ~ .form-check-label {
color: $text-muted;
}
}
.form-check-label {
margin-bottom: 0; // Override default `<label>` bottom margin
}
.form-check-inline {
display: inline-flex;
align-items: center;
padding-left: 0; // Override base .form-check
margin-right: $form-check-inline-margin-x;
// Undo .form-check-input defaults and add some `margin-right`.
.form-check-input {
position: static;
margin-top: 0;
margin-right: $form-check-inline-input-margin-x;
margin-left: 0;
}
}
// Form validation
//
// Provide feedback to users when form field values are valid or invalid. Works
// primarily for client-side validation via scoped `:invalid` and `:valid`
// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for
// server side validation.
@each $state, $data in $form-validation-states {
@include form-validation-state($state, map-get($data, color), map-get($data, icon));
}
// Inline forms
//
// Make forms appear inline(-block) by adding the `.form-inline` class. Inline
// forms begin stacked on extra small (mobile) devices and then go inline when
// viewports reach <768px.
//
// Requires wrapping inputs and labels with `.form-group` for proper display of
// default HTML form controls and our custom form controls (e.g., input groups).
.form-inline {
display: flex;
flex-flow: row wrap;
align-items: center; // Prevent shorter elements from growing to same height as others (e.g., small buttons growing to normal sized button height)
// Because we use flex, the initial sizing of checkboxes is collapsed and
// doesn't occupy the full-width (which is what we want for xs grid tier),
// so we force that here.
.form-check {
width: 100%;
}
// Kick in the inline
@include media-breakpoint-up(sm) {
label {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0;
}
// Inline-block all the things for "inline"
.form-group {
display: flex;
flex: 0 0 auto;
flex-flow: row wrap;
align-items: center;
margin-bottom: 0;
}
// Allow folks to *not* use `.form-group`
.form-control {
display: inline-block;
width: auto; // Prevent labels from stacking above inputs in `.form-group`
vertical-align: middle;
}
// Make static controls behave like regular ones
.form-control-plaintext {
display: inline-block;
}
.input-group,
.custom-select {
width: auto;
}
// Remove default margin on radios/checkboxes that were used for stacking, and
// then undo the floating of radios and checkboxes to match.
.form-check {
display: flex;
align-items: center;
justify-content: center;
width: auto;
padding-left: 0;
}
.form-check-input {
position: relative;
flex-shrink: 0;
margin-top: 0;
margin-right: $form-check-input-margin-x;
margin-left: 0;
}
.custom-control {
align-items: center;
justify-content: center;
}
.custom-control-label {
margin-bottom: 0;
}
}
}

View File

@@ -0,0 +1,73 @@
// Container widths
//
// Set the container width, and override it for fixed navbars in media queries.
@if $enable-grid-classes {
// Single container class with breakpoint max-widths
.container,
// 100% wide container at all breakpoints
.container-fluid {
@include make-container();
}
// Responsive containers that are 100% wide until a breakpoint
@each $breakpoint, $container-max-width in $container-max-widths {
.container-#{$breakpoint} {
@extend .container-fluid;
}
@include media-breakpoint-up($breakpoint, $grid-breakpoints) {
%responsive-container-#{$breakpoint} {
max-width: $container-max-width;
}
// Extend each breakpoint which is smaller or equal to the current breakpoint
$extend-breakpoint: true;
@each $name, $width in $grid-breakpoints {
@if ($extend-breakpoint) {
.container#{breakpoint-infix($name, $grid-breakpoints)} {
@extend %responsive-container-#{$breakpoint};
}
// Once the current breakpoint is reached, stop extending
@if ($breakpoint == $name) {
$extend-breakpoint: false;
}
}
}
}
}
}
// Row
//
// Rows contain your columns.
@if $enable-grid-classes {
.row {
@include make-row();
}
// Remove the negative margin from default .row, then the horizontal padding
// from all immediate children columns (to prevent runaway style inheritance).
.no-gutters {
margin-right: 0;
margin-left: 0;
> .col,
> [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
}
}
// Columns
//
// Common styles for small and large grid columns
@if $enable-grid-classes {
@include make-grid-columns();
}

View File

@@ -0,0 +1,42 @@
// Responsive images (ensure images don't scale beyond their parents)
//
// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s.
// We previously tried the "images are responsive by default" approach in Bootstrap v2,
// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)
// which weren't expecting the images within themselves to be involuntarily resized.
// See also https://github.com/twbs/bootstrap/issues/18178
.img-fluid {
@include img-fluid();
}
// Image thumbnails
.img-thumbnail {
padding: $thumbnail-padding;
background-color: $thumbnail-bg;
border: $thumbnail-border-width solid $thumbnail-border-color;
@include border-radius($thumbnail-border-radius);
@include box-shadow($thumbnail-box-shadow);
// Keep them at most 100% wide
@include img-fluid();
}
//
// Figures
//
.figure {
// Ensures the caption's text aligns with the image.
display: inline-block;
}
.figure-img {
margin-bottom: $spacer / 2;
line-height: 1;
}
.figure-caption {
@include font-size($figure-caption-font-size);
color: $figure-caption-color;
}

View File

@@ -0,0 +1,192 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.input-group {
position: relative;
display: flex;
flex-wrap: wrap; // For form validation feedback
align-items: stretch;
width: 100%;
> .form-control,
> .form-control-plaintext,
> .custom-select,
> .custom-file {
position: relative; // For focus state's z-index
flex: 1 1 auto;
width: 1%;
min-width: 0; // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
margin-bottom: 0;
+ .form-control,
+ .custom-select,
+ .custom-file {
margin-left: -$input-border-width;
}
}
// Bring the "active" form control to the top of surrounding elements
> .form-control:focus,
> .custom-select:focus,
> .custom-file .custom-file-input:focus ~ .custom-file-label {
z-index: 3;
}
// Bring the custom file input above the label
> .custom-file .custom-file-input:focus {
z-index: 4;
}
> .form-control,
> .custom-select {
&:not(:last-child) { @include border-right-radius(0); }
&:not(:first-child) { @include border-left-radius(0); }
}
// Custom file inputs have more complex markup, thus requiring different
// border-radius overrides.
> .custom-file {
display: flex;
align-items: center;
&:not(:last-child) .custom-file-label,
&:not(:last-child) .custom-file-label::after { @include border-right-radius(0); }
&:not(:first-child) .custom-file-label { @include border-left-radius(0); }
}
}
// Prepend and append
//
// While it requires one extra layer of HTML for each, dedicated prepend and
// append elements allow us to 1) be less clever, 2) simplify our selectors, and
// 3) support HTML5 form validation.
.input-group-prepend,
.input-group-append {
display: flex;
// Ensure buttons are always above inputs for more visually pleasing borders.
// This isn't needed for `.input-group-text` since it shares the same border-color
// as our inputs.
.btn {
position: relative;
z-index: 2;
&:focus {
z-index: 3;
}
}
.btn + .btn,
.btn + .input-group-text,
.input-group-text + .input-group-text,
.input-group-text + .btn {
margin-left: -$input-border-width;
}
}
.input-group-prepend { margin-right: -$input-border-width; }
.input-group-append { margin-left: -$input-border-width; }
// Textual addons
//
// Serves as a catch-all element for any text or radio/checkbox input you wish
// to prepend or append to an input.
.input-group-text {
display: flex;
align-items: center;
padding: $input-padding-y $input-padding-x;
margin-bottom: 0; // Allow use of <label> elements by overriding our default margin-bottom
@include font-size($input-font-size); // Match inputs
font-weight: $font-weight-normal;
line-height: $input-line-height;
color: $input-group-addon-color;
text-align: center;
white-space: nowrap;
background-color: $input-group-addon-bg;
border: $input-border-width solid $input-group-addon-border-color;
@include border-radius($input-border-radius);
// Nuke default margins from checkboxes and radios to vertically center within.
input[type="radio"],
input[type="checkbox"] {
margin-top: 0;
}
}
// Sizing
//
// Remix the default form control sizing classes into new ones for easier
// manipulation.
.input-group-lg > .form-control:not(textarea),
.input-group-lg > .custom-select {
height: $input-height-lg;
}
.input-group-lg > .form-control,
.input-group-lg > .custom-select,
.input-group-lg > .input-group-prepend > .input-group-text,
.input-group-lg > .input-group-append > .input-group-text,
.input-group-lg > .input-group-prepend > .btn,
.input-group-lg > .input-group-append > .btn {
padding: $input-padding-y-lg $input-padding-x-lg;
@include font-size($input-font-size-lg);
line-height: $input-line-height-lg;
@include border-radius($input-border-radius-lg);
}
.input-group-sm > .form-control:not(textarea),
.input-group-sm > .custom-select {
height: $input-height-sm;
}
.input-group-sm > .form-control,
.input-group-sm > .custom-select,
.input-group-sm > .input-group-prepend > .input-group-text,
.input-group-sm > .input-group-append > .input-group-text,
.input-group-sm > .input-group-prepend > .btn,
.input-group-sm > .input-group-append > .btn {
padding: $input-padding-y-sm $input-padding-x-sm;
@include font-size($input-font-size-sm);
line-height: $input-line-height-sm;
@include border-radius($input-border-radius-sm);
}
.input-group-lg > .custom-select,
.input-group-sm > .custom-select {
padding-right: $custom-select-padding-x + $custom-select-indicator-padding;
}
// Prepend and append rounded corners
//
// These rulesets must come after the sizing ones to properly override sm and lg
// border-radius values when extending. They're more specific than we'd like
// with the `.input-group >` part, but without it, we cannot override the sizing.
.input-group > .input-group-prepend > .btn,
.input-group > .input-group-prepend > .input-group-text,
.input-group > .input-group-append:not(:last-child) > .btn,
.input-group > .input-group-append:not(:last-child) > .input-group-text,
.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),
.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {
@include border-right-radius(0);
}
.input-group > .input-group-append > .btn,
.input-group > .input-group-append > .input-group-text,
.input-group > .input-group-prepend:not(:first-child) > .btn,
.input-group > .input-group-prepend:not(:first-child) > .input-group-text,
.input-group > .input-group-prepend:first-child > .btn:not(:first-child),
.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {
@include border-left-radius(0);
}

View File

@@ -0,0 +1,17 @@
.jumbotron {
padding: $jumbotron-padding ($jumbotron-padding / 2);
margin-bottom: $jumbotron-padding;
color: $jumbotron-color;
background-color: $jumbotron-bg;
@include border-radius($border-radius-lg);
@include media-breakpoint-up(sm) {
padding: ($jumbotron-padding * 2) $jumbotron-padding;
}
}
.jumbotron-fluid {
padding-right: 0;
padding-left: 0;
@include border-radius(0);
}

View File

@@ -0,0 +1,154 @@
// Base class
//
// Easily usable on <ul>, <ol>, or <div>.
.list-group {
display: flex;
flex-direction: column;
// No need to set list-style: none; since .list-group-item is block level
padding-left: 0; // reset padding because ul and ol
margin-bottom: 0;
@include border-radius($list-group-border-radius);
}
// Interactive list items
//
// Use anchor or button elements instead of `li`s or `div`s to create interactive
// list items. Includes an extra `.active` modifier class for selected items.
.list-group-item-action {
width: 100%; // For `<button>`s (anchors become 100% by default though)
color: $list-group-action-color;
text-align: inherit; // For `<button>`s (anchors inherit)
// Hover state
@include hover-focus() {
z-index: 1; // Place hover/focus items above their siblings for proper border styling
color: $list-group-action-hover-color;
text-decoration: none;
background-color: $list-group-hover-bg;
}
&:active {
color: $list-group-action-active-color;
background-color: $list-group-action-active-bg;
}
}
// Individual list items
//
// Use on `li`s or `div`s within the `.list-group` parent.
.list-group-item {
position: relative;
display: block;
padding: $list-group-item-padding-y $list-group-item-padding-x;
color: $list-group-color;
text-decoration: if($link-decoration == none, null, none);
background-color: $list-group-bg;
border: $list-group-border-width solid $list-group-border-color;
&:first-child {
@include border-top-radius(inherit);
}
&:last-child {
@include border-bottom-radius(inherit);
}
&.disabled,
&:disabled {
color: $list-group-disabled-color;
pointer-events: none;
background-color: $list-group-disabled-bg;
}
// Include both here for `<a>`s and `<button>`s
&.active {
z-index: 2; // Place active items above their siblings for proper border styling
color: $list-group-active-color;
background-color: $list-group-active-bg;
border-color: $list-group-active-border-color;
}
& + & {
border-top-width: 0;
&.active {
margin-top: -$list-group-border-width;
border-top-width: $list-group-border-width;
}
}
}
// Horizontal
//
// Change the layout of list group items from vertical (default) to horizontal.
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.list-group-horizontal#{$infix} {
flex-direction: row;
> .list-group-item {
&:first-child {
@include border-bottom-left-radius($list-group-border-radius);
@include border-top-right-radius(0);
}
&:last-child {
@include border-top-right-radius($list-group-border-radius);
@include border-bottom-left-radius(0);
}
&.active {
margin-top: 0;
}
+ .list-group-item {
border-top-width: $list-group-border-width;
border-left-width: 0;
&.active {
margin-left: -$list-group-border-width;
border-left-width: $list-group-border-width;
}
}
}
}
}
}
// Flush list items
//
// Remove borders and border-radius to keep list group items edge-to-edge. Most
// useful within other components (e.g., cards).
.list-group-flush {
@include border-radius(0);
> .list-group-item {
border-width: 0 0 $list-group-border-width;
&:last-child {
border-bottom-width: 0;
}
}
}
// Contextual variants
//
// Add modifier classes to change text and background color on individual items.
// Organizationally, this must come after the `:hover` states.
@each $color, $value in $theme-colors {
@include list-group-item-variant($color, theme-color-level($color, -9), theme-color-level($color, 6));
}

View File

@@ -0,0 +1,8 @@
.media {
display: flex;
align-items: flex-start;
}
.media-body {
flex: 1;
}

View File

@@ -0,0 +1,240 @@
// .modal-open - body class for killing the scroll
// .modal - container to scroll within
// .modal-dialog - positioning shell for the actual modal
// .modal-content - actual modal w/ bg and corners and stuff
.modal-open {
// Kill the scroll on the body
overflow: hidden;
.modal {
overflow-x: hidden;
overflow-y: auto;
}
}
// Container that the modal scrolls within
.modal {
position: fixed;
top: 0;
left: 0;
z-index: $zindex-modal;
display: none;
width: 100%;
height: 100%;
overflow: hidden;
// Prevent Chrome on Windows from adding a focus outline. For details, see
// https://github.com/twbs/bootstrap/pull/10951.
outline: 0;
// We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a
// gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342
// See also https://github.com/twbs/bootstrap/issues/17695
}
// Shell div to position the modal with bottom padding
.modal-dialog {
position: relative;
width: auto;
margin: $modal-dialog-margin;
// allow clicks to pass through for custom click handling to close modal
pointer-events: none;
// When fading in the modal, animate it to slide down
.modal.fade & {
@include transition($modal-transition);
transform: $modal-fade-transform;
}
.modal.show & {
transform: $modal-show-transform;
}
// When trying to close, animate focus to scale
.modal.modal-static & {
transform: $modal-scale-transform;
}
}
.modal-dialog-scrollable {
display: flex; // IE10/11
max-height: subtract(100%, $modal-dialog-margin * 2);
.modal-content {
max-height: subtract(100vh, $modal-dialog-margin * 2); // IE10/11
overflow: hidden;
}
.modal-header,
.modal-footer {
flex-shrink: 0;
}
.modal-body {
overflow-y: auto;
}
}
.modal-dialog-centered {
display: flex;
align-items: center;
min-height: subtract(100%, $modal-dialog-margin * 2);
// Ensure `modal-dialog-centered` extends the full height of the view (IE10/11)
&::before {
display: block; // IE10
height: subtract(100vh, $modal-dialog-margin * 2);
height: min-content; // Reset height to 0 except on IE
content: "";
}
// Ensure `.modal-body` shows scrollbar (IE10/11)
&.modal-dialog-scrollable {
flex-direction: column;
justify-content: center;
height: 100%;
.modal-content {
max-height: none;
}
&::before {
content: none;
}
}
}
// Actual modal
.modal-content {
position: relative;
display: flex;
flex-direction: column;
width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog`
// counteract the pointer-events: none; in the .modal-dialog
color: $modal-content-color;
pointer-events: auto;
background-color: $modal-content-bg;
background-clip: padding-box;
border: $modal-content-border-width solid $modal-content-border-color;
@include border-radius($modal-content-border-radius);
@include box-shadow($modal-content-box-shadow-xs);
// Remove focus outline from opened modal
outline: 0;
}
// Modal background
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
z-index: $zindex-modal-backdrop;
width: 100vw;
height: 100vh;
background-color: $modal-backdrop-bg;
// Fade for backdrop
&.fade { opacity: 0; }
&.show { opacity: $modal-backdrop-opacity; }
}
// Modal header
// Top section of the modal w/ title and dismiss
.modal-header {
display: flex;
align-items: flex-start; // so the close btn always stays on the upper right corner
justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends
padding: $modal-header-padding;
border-bottom: $modal-header-border-width solid $modal-header-border-color;
@include border-top-radius($modal-content-inner-border-radius);
.close {
padding: $modal-header-padding;
// auto on the left force icon to the right even when there is no .modal-title
margin: (-$modal-header-padding-y) (-$modal-header-padding-x) (-$modal-header-padding-y) auto;
}
}
// Title text within header
.modal-title {
margin-bottom: 0;
line-height: $modal-title-line-height;
}
// Modal body
// Where all modal content resides (sibling of .modal-header and .modal-footer)
.modal-body {
position: relative;
// Enable `flex-grow: 1` so that the body take up as much space as possible
// when there should be a fixed height on `.modal-dialog`.
flex: 1 1 auto;
padding: $modal-inner-padding;
}
// Footer (for actions)
.modal-footer {
display: flex;
flex-wrap: wrap;
align-items: center; // vertically center
justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items
padding: $modal-inner-padding - $modal-footer-margin-between / 2;
border-top: $modal-footer-border-width solid $modal-footer-border-color;
@include border-bottom-radius($modal-content-inner-border-radius);
// Place margin between footer elements
// This solution is far from ideal because of the universal selector usage,
// but is needed to fix https://github.com/twbs/bootstrap/issues/24800
> * {
margin: $modal-footer-margin-between / 2;
}
}
// Measure scrollbar width for padding body during modal show/hide
.modal-scrollbar-measure {
position: absolute;
top: -9999px;
width: 50px;
height: 50px;
overflow: scroll;
}
// Scale up the modal
@include media-breakpoint-up(sm) {
// Automatically set modal's width for larger viewports
.modal-dialog {
max-width: $modal-md;
margin: $modal-dialog-margin-y-sm-up auto;
}
.modal-dialog-scrollable {
max-height: subtract(100%, $modal-dialog-margin-y-sm-up * 2);
.modal-content {
max-height: subtract(100vh, $modal-dialog-margin-y-sm-up * 2);
}
}
.modal-dialog-centered {
min-height: subtract(100%, $modal-dialog-margin-y-sm-up * 2);
&::before {
height: subtract(100vh, $modal-dialog-margin-y-sm-up * 2);
height: min-content;
}
}
.modal-content {
@include box-shadow($modal-content-box-shadow-sm-up);
}
.modal-sm { max-width: $modal-sm; }
}
@include media-breakpoint-up(lg) {
.modal-lg,
.modal-xl {
max-width: $modal-lg;
}
}
@include media-breakpoint-up(xl) {
.modal-xl { max-width: $modal-xl; }
}

View File

@@ -0,0 +1,123 @@
// Base class
//
// Kickstart any navigation component with a set of style resets. Works with
// `<nav>`s, `<ul>`s or `<ol>`s.
.nav {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none;
}
.nav-link {
display: block;
padding: $nav-link-padding-y $nav-link-padding-x;
text-decoration: if($link-decoration == none, null, none);
@include hover-focus() {
text-decoration: none;
}
// Disabled state lightens text
&.disabled {
color: $nav-link-disabled-color;
pointer-events: none;
cursor: default;
}
}
//
// Tabs
//
.nav-tabs {
border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color;
.nav-item {
margin-bottom: -$nav-tabs-border-width;
}
.nav-link {
border: $nav-tabs-border-width solid transparent;
@include border-top-radius($nav-tabs-border-radius);
@include hover-focus() {
border-color: $nav-tabs-link-hover-border-color;
}
&.disabled {
color: $nav-link-disabled-color;
background-color: transparent;
border-color: transparent;
}
}
.nav-link.active,
.nav-item.show .nav-link {
color: $nav-tabs-link-active-color;
background-color: $nav-tabs-link-active-bg;
border-color: $nav-tabs-link-active-border-color;
}
.dropdown-menu {
// Make dropdown border overlap tab border
margin-top: -$nav-tabs-border-width;
// Remove the top rounded corners here since there is a hard edge above the menu
@include border-top-radius(0);
}
}
//
// Pills
//
.nav-pills {
.nav-link {
@include border-radius($nav-pills-border-radius);
}
.nav-link.active,
.show > .nav-link {
color: $nav-pills-link-active-color;
background-color: $nav-pills-link-active-bg;
}
}
//
// Justified variants
//
.nav-fill {
> .nav-link,
.nav-item {
flex: 1 1 auto;
text-align: center;
}
}
.nav-justified {
> .nav-link,
.nav-item {
flex-basis: 0;
flex-grow: 1;
text-align: center;
}
}
// Tabbable tabs
//
// Hide tabbable panes to start, show them when `.active`
.tab-content {
> .tab-pane {
display: none;
}
> .active {
display: block;
}
}

View File

@@ -0,0 +1,324 @@
// Contents
//
// Navbar
// Navbar brand
// Navbar nav
// Navbar text
// Navbar divider
// Responsive navbar
// Navbar position
// Navbar themes
// Navbar
//
// Provide a static navbar from which we expand to create full-width, fixed, and
// other navbar variations.
.navbar {
position: relative;
display: flex;
flex-wrap: wrap; // allow us to do the line break for collapsing content
align-items: center;
justify-content: space-between; // space out brand from logo
padding: $navbar-padding-y $navbar-padding-x;
// Because flex properties aren't inherited, we need to redeclare these first
// few properties so that content nested within behave properly.
%container-flex-properties {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
.container,
.container-fluid {
@extend %container-flex-properties;
}
@each $breakpoint, $container-max-width in $container-max-widths {
> .container#{breakpoint-infix($breakpoint, $container-max-widths)} {
@extend %container-flex-properties;
}
}
}
// Navbar brand
//
// Used for brand, project, or site names.
.navbar-brand {
display: inline-block;
padding-top: $navbar-brand-padding-y;
padding-bottom: $navbar-brand-padding-y;
margin-right: $navbar-padding-x;
@include font-size($navbar-brand-font-size);
line-height: inherit;
white-space: nowrap;
@include hover-focus() {
text-decoration: none;
}
}
// Navbar nav
//
// Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`).
.navbar-nav {
display: flex;
flex-direction: column; // cannot use `inherit` to get the `.navbar`s value
padding-left: 0;
margin-bottom: 0;
list-style: none;
.nav-link {
padding-right: 0;
padding-left: 0;
}
.dropdown-menu {
position: static;
float: none;
}
}
// Navbar text
//
//
.navbar-text {
display: inline-block;
padding-top: $nav-link-padding-y;
padding-bottom: $nav-link-padding-y;
}
// Responsive navbar
//
// Custom styles for responsive collapsing and toggling of navbar contents.
// Powered by the collapse Bootstrap JavaScript plugin.
// When collapsed, prevent the toggleable navbar contents from appearing in
// the default flexbox row orientation. Requires the use of `flex-wrap: wrap`
// on the `.navbar` parent.
.navbar-collapse {
flex-basis: 100%;
flex-grow: 1;
// For always expanded or extra full navbars, ensure content aligns itself
// properly vertically. Can be easily overridden with flex utilities.
align-items: center;
}
// Button for toggling the navbar when in its collapsed state
.navbar-toggler {
padding: $navbar-toggler-padding-y $navbar-toggler-padding-x;
@include font-size($navbar-toggler-font-size);
line-height: 1;
background-color: transparent; // remove default button style
border: $border-width solid transparent; // remove default button style
@include border-radius($navbar-toggler-border-radius);
@include hover-focus() {
text-decoration: none;
}
}
// Keep as a separate element so folks can easily override it with another icon
// or image file as needed.
.navbar-toggler-icon {
display: inline-block;
width: 1.5em;
height: 1.5em;
vertical-align: middle;
content: "";
background: no-repeat center center;
background-size: 100% 100%;
}
// Generate series of `.navbar-expand-*` responsive classes for configuring
// where your navbar collapses.
.navbar-expand {
@each $breakpoint in map-keys($grid-breakpoints) {
$next: breakpoint-next($breakpoint, $grid-breakpoints);
$infix: breakpoint-infix($next, $grid-breakpoints);
&#{$infix} {
@include media-breakpoint-down($breakpoint) {
%container-navbar-expand-#{$breakpoint} {
padding-right: 0;
padding-left: 0;
}
> .container,
> .container-fluid {
@extend %container-navbar-expand-#{$breakpoint};
}
@each $size, $container-max-width in $container-max-widths {
> .container#{breakpoint-infix($size, $container-max-widths)} {
@extend %container-navbar-expand-#{$breakpoint};
}
}
}
@include media-breakpoint-up($next) {
flex-flow: row nowrap;
justify-content: flex-start;
.navbar-nav {
flex-direction: row;
.dropdown-menu {
position: absolute;
}
.nav-link {
padding-right: $navbar-nav-link-padding-x;
padding-left: $navbar-nav-link-padding-x;
}
}
// For nesting containers, have to redeclare for alignment purposes
%container-nesting-#{$breakpoint} {
flex-wrap: nowrap;
}
> .container,
> .container-fluid {
@extend %container-nesting-#{$breakpoint};
}
@each $size, $container-max-width in $container-max-widths {
> .container#{breakpoint-infix($size, $container-max-widths)} {
@extend %container-nesting-#{$breakpoint};
}
}
.navbar-collapse {
display: flex !important; // stylelint-disable-line declaration-no-important
// Changes flex-bases to auto because of an IE10 bug
flex-basis: auto;
}
.navbar-toggler {
display: none;
}
}
}
}
}
// Navbar themes
//
// Styles for switching between navbars with light or dark background.
// Dark links against a light background
.navbar-light {
.navbar-brand {
color: $navbar-light-brand-color;
@include hover-focus() {
color: $navbar-light-brand-hover-color;
}
}
.navbar-nav {
.nav-link {
color: $navbar-light-color;
@include hover-focus() {
color: $navbar-light-hover-color;
}
&.disabled {
color: $navbar-light-disabled-color;
}
}
.show > .nav-link,
.active > .nav-link,
.nav-link.show,
.nav-link.active {
color: $navbar-light-active-color;
}
}
.navbar-toggler {
color: $navbar-light-color;
border-color: $navbar-light-toggler-border-color;
}
.navbar-toggler-icon {
background-image: escape-svg($navbar-light-toggler-icon-bg);
}
.navbar-text {
color: $navbar-light-color;
a {
color: $navbar-light-active-color;
@include hover-focus() {
color: $navbar-light-active-color;
}
}
}
}
// White links against a dark background
.navbar-dark {
.navbar-brand {
color: $navbar-dark-brand-color;
@include hover-focus() {
color: $navbar-dark-brand-hover-color;
}
}
.navbar-nav {
.nav-link {
color: $navbar-dark-color;
@include hover-focus() {
color: $navbar-dark-hover-color;
}
&.disabled {
color: $navbar-dark-disabled-color;
}
}
.show > .nav-link,
.active > .nav-link,
.nav-link.show,
.nav-link.active {
color: $navbar-dark-active-color;
}
}
.navbar-toggler {
color: $navbar-dark-color;
border-color: $navbar-dark-toggler-border-color;
}
.navbar-toggler-icon {
background-image: escape-svg($navbar-dark-toggler-icon-bg);
}
.navbar-text {
color: $navbar-dark-color;
a {
color: $navbar-dark-active-color;
@include hover-focus() {
color: $navbar-dark-active-color;
}
}
}
}

View File

@@ -0,0 +1,286 @@
//
// Base styles
//
.card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106
height: $card-height;
word-wrap: break-word;
background-color: $card-bg;
background-clip: border-box;
border: $card-border-width solid $card-border-color;
@include border-radius($card-border-radius);
> hr {
margin-right: 0;
margin-left: 0;
}
> .list-group {
border-top: inherit;
border-bottom: inherit;
&:first-child {
border-top-width: 0;
@include border-top-radius($card-inner-border-radius);
}
&:last-child {
border-bottom-width: 0;
@include border-bottom-radius($card-inner-border-radius);
}
}
// Due to specificity of the above selector (`.card > .list-group`), we must
// use a child selector here to prevent double borders.
> .card-header + .list-group,
> .list-group + .card-footer {
border-top: 0;
}
}
.card-body {
// Enable `flex-grow: 1` for decks and groups so that card blocks take up
// as much space as possible, ensuring footers are aligned to the bottom.
flex: 1 1 auto;
// Workaround for the image size bug in IE
// See: https://github.com/twbs/bootstrap/pull/28855
min-height: 1px;
padding: $card-spacer-x;
color: $card-color;
}
.card-title {
margin-bottom: $card-spacer-y;
}
.card-subtitle {
margin-top: -$card-spacer-y / 2;
margin-bottom: 0;
}
.card-text:last-child {
margin-bottom: 0;
}
.card-link {
@include hover() {
text-decoration: none;
}
+ .card-link {
margin-left: $card-spacer-x;
}
}
//
// Optional textual caps
//
.card-header {
padding: $card-spacer-y $card-spacer-x;
margin-bottom: 0; // Removes the default margin-bottom of <hN>
color: $card-cap-color;
background-color: $card-cap-bg;
border-bottom: $card-border-width solid $card-border-color;
&:first-child {
@include border-radius($card-inner-border-radius $card-inner-border-radius 0 0);
}
}
.card-footer {
padding: $card-spacer-y $card-spacer-x;
color: $card-cap-color;
background-color: $card-cap-bg;
border-top: $card-border-width solid $card-border-color;
&:last-child {
@include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius);
}
}
//
// Header navs
//
.card-header-tabs {
margin-right: -$card-spacer-x / 2;
margin-bottom: -$card-spacer-y;
margin-left: -$card-spacer-x / 2;
border-bottom: 0;
}
.card-header-pills {
margin-right: -$card-spacer-x / 2;
margin-left: -$card-spacer-x / 2;
}
// Card image
.card-img-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: $card-img-overlay-padding;
@include border-radius($card-inner-border-radius);
}
.card-img,
.card-img-top,
.card-img-bottom {
flex-shrink: 0; // For IE: https://github.com/twbs/bootstrap/issues/29396
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
}
.card-img,
.card-img-top {
@include border-top-radius($card-inner-border-radius);
}
.card-img,
.card-img-bottom {
@include border-bottom-radius($card-inner-border-radius);
}
// Card deck
.card-deck {
.card {
margin-bottom: $card-deck-margin;
}
@include media-breakpoint-up(sm) {
display: flex;
flex-flow: row wrap;
margin-right: -$card-deck-margin;
margin-left: -$card-deck-margin;
.card {
// Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4
flex: 1 0 0%;
margin-right: $card-deck-margin;
margin-bottom: 0; // Override the default
margin-left: $card-deck-margin;
}
}
}
//
// Card groups
//
.card-group {
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
margin-bottom: $card-group-margin;
}
@include media-breakpoint-up(sm) {
display: flex;
flex-flow: row wrap;
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
// Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4
flex: 1 0 0%;
margin-bottom: 0;
+ .card {
margin-left: 0;
border-left: 0;
}
// Handle rounded corners
@if $enable-rounded {
&:not(:last-child) {
@include border-right-radius(0);
.card-img-top,
.card-header {
// stylelint-disable-next-line property-disallowed-list
border-top-right-radius: 0;
}
.card-img-bottom,
.card-footer {
// stylelint-disable-next-line property-disallowed-list
border-bottom-right-radius: 0;
}
}
&:not(:first-child) {
@include border-left-radius(0);
.card-img-top,
.card-header {
// stylelint-disable-next-line property-disallowed-list
border-top-left-radius: 0;
}
.card-img-bottom,
.card-footer {
// stylelint-disable-next-line property-disallowed-list
border-bottom-left-radius: 0;
}
}
}
}
}
}
//
// Columns
//
.card-columns {
.card {
margin-bottom: $card-columns-margin;
}
@include media-breakpoint-up(sm) {
column-count: $card-columns-count;
column-gap: $card-columns-gap;
orphans: 1;
widows: 1;
.card {
display: inline-block; // Don't let them vertically span multiple columns
width: 100%; // Don't let their width change
}
}
}
//
// Accordion
//
.accordion {
overflow-anchor: none;
> .card {
overflow: hidden;
&:not(:last-of-type) {
border-bottom: 0;
@include border-bottom-radius(0);
}
&:not(:first-of-type) {
@include border-top-radius(0);
}
> .card-header {
@include border-radius(0);
margin-bottom: -$card-border-width;
}
}
}

View File

@@ -0,0 +1,74 @@
.pagination {
display: flex;
@include list-unstyled();
@include border-radius();
}
.page-link {
position: relative;
display: block;
padding: $pagination-padding-y $pagination-padding-x;
margin-left: -$pagination-border-width;
line-height: $pagination-line-height;
color: $pagination-color;
text-decoration: if($link-decoration == none, null, none);
background-color: $pagination-bg;
border: $pagination-border-width solid $pagination-border-color;
&:hover {
z-index: 2;
color: $pagination-hover-color;
text-decoration: none;
background-color: $pagination-hover-bg;
border-color: $pagination-hover-border-color;
}
&:focus {
z-index: 3;
outline: $pagination-focus-outline;
box-shadow: $pagination-focus-box-shadow;
}
}
.page-item {
&:first-child {
.page-link {
margin-left: 0;
@include border-left-radius($border-radius);
}
}
&:last-child {
.page-link {
@include border-right-radius($border-radius);
}
}
&.active .page-link {
z-index: 3;
color: $pagination-active-color;
background-color: $pagination-active-bg;
border-color: $pagination-active-border-color;
}
&.disabled .page-link {
color: $pagination-disabled-color;
pointer-events: none;
// Opinionated: remove the "hand" cursor set previously for .page-link
cursor: auto;
background-color: $pagination-disabled-bg;
border-color: $pagination-disabled-border-color;
}
}
//
// Sizing
//
.pagination-lg {
@include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $line-height-lg, $border-radius-lg);
}
.pagination-sm {
@include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $line-height-sm, $border-radius-sm);
}

View File

@@ -0,0 +1,170 @@
.popover {
position: absolute;
top: 0;
left: 0;
z-index: $zindex-popover;
display: block;
max-width: $popover-max-width;
// Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.
// So reset our font and text properties to avoid inheriting weird values.
@include reset-text();
@include font-size($popover-font-size);
// Allow breaking very long words so they don't overflow the popover's bounds
word-wrap: break-word;
background-color: $popover-bg;
background-clip: padding-box;
border: $popover-border-width solid $popover-border-color;
@include border-radius($popover-border-radius);
@include box-shadow($popover-box-shadow);
.arrow {
position: absolute;
display: block;
width: $popover-arrow-width;
height: $popover-arrow-height;
margin: 0 $popover-border-radius;
&::before,
&::after {
position: absolute;
display: block;
content: "";
border-color: transparent;
border-style: solid;
}
}
}
.bs-popover-top {
margin-bottom: $popover-arrow-height;
> .arrow {
bottom: subtract(-$popover-arrow-height, $popover-border-width);
&::before {
bottom: 0;
border-width: $popover-arrow-height ($popover-arrow-width / 2) 0;
border-top-color: $popover-arrow-outer-color;
}
&::after {
bottom: $popover-border-width;
border-width: $popover-arrow-height ($popover-arrow-width / 2) 0;
border-top-color: $popover-arrow-color;
}
}
}
.bs-popover-right {
margin-left: $popover-arrow-height;
> .arrow {
left: subtract(-$popover-arrow-height, $popover-border-width);
width: $popover-arrow-height;
height: $popover-arrow-width;
margin: $popover-border-radius 0; // make sure the arrow does not touch the popover's rounded corners
&::before {
left: 0;
border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0;
border-right-color: $popover-arrow-outer-color;
}
&::after {
left: $popover-border-width;
border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0;
border-right-color: $popover-arrow-color;
}
}
}
.bs-popover-bottom {
margin-top: $popover-arrow-height;
> .arrow {
top: subtract(-$popover-arrow-height, $popover-border-width);
&::before {
top: 0;
border-width: 0 ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2);
border-bottom-color: $popover-arrow-outer-color;
}
&::after {
top: $popover-border-width;
border-width: 0 ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2);
border-bottom-color: $popover-arrow-color;
}
}
// This will remove the popover-header's border just below the arrow
.popover-header::before {
position: absolute;
top: 0;
left: 50%;
display: block;
width: $popover-arrow-width;
margin-left: -$popover-arrow-width / 2;
content: "";
border-bottom: $popover-border-width solid $popover-header-bg;
}
}
.bs-popover-left {
margin-right: $popover-arrow-height;
> .arrow {
right: subtract(-$popover-arrow-height, $popover-border-width);
width: $popover-arrow-height;
height: $popover-arrow-width;
margin: $popover-border-radius 0; // make sure the arrow does not touch the popover's rounded corners
&::before {
right: 0;
border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height;
border-left-color: $popover-arrow-outer-color;
}
&::after {
right: $popover-border-width;
border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height;
border-left-color: $popover-arrow-color;
}
}
}
.bs-popover-auto {
&[x-placement^="top"] {
@extend .bs-popover-top;
}
&[x-placement^="right"] {
@extend .bs-popover-right;
}
&[x-placement^="bottom"] {
@extend .bs-popover-bottom;
}
&[x-placement^="left"] {
@extend .bs-popover-left;
}
}
// Offset the popover to account for the popover arrow
.popover-header {
padding: $popover-header-padding-y $popover-header-padding-x;
margin-bottom: 0; // Reset the default from Reboot
@include font-size($font-size-base);
color: $popover-header-color;
background-color: $popover-header-bg;
border-bottom: $popover-border-width solid darken($popover-header-bg, 5%);
@include border-top-radius($popover-inner-border-radius);
&:empty {
display: none;
}
}
.popover-body {
padding: $popover-body-padding-y $popover-body-padding-x;
color: $popover-body-color;
}

View File

@@ -0,0 +1,47 @@
// Disable animation if transitions are disabled
@if $enable-transitions {
@keyframes progress-bar-stripes {
from { background-position: $progress-height 0; }
to { background-position: 0 0; }
}
}
.progress {
display: flex;
height: $progress-height;
overflow: hidden; // force rounded corners by cropping it
line-height: 0;
@include font-size($progress-font-size);
background-color: $progress-bg;
@include border-radius($progress-border-radius);
@include box-shadow($progress-box-shadow);
}
.progress-bar {
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
color: $progress-bar-color;
text-align: center;
white-space: nowrap;
background-color: $progress-bar-bg;
@include transition($progress-bar-transition);
}
.progress-bar-striped {
@include gradient-striped();
background-size: $progress-height $progress-height;
}
@if $enable-transitions {
.progress-bar-animated {
animation: progress-bar-stripes $progress-bar-animation-timing;
@if $enable-prefers-reduced-motion-media-query {
@media (prefers-reduced-motion: reduce) {
animation: none;
}
}
}
}

View File

@@ -0,0 +1,484 @@
// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix
// Reboot
//
// Normalization of HTML elements, manually forked from Normalize.css to remove
// styles targeting irrelevant browsers while applying new styles.
//
// Normalize is licensed MIT. https://github.com/necolas/normalize.css
// Document
//
// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.
// 2. Change the default font family in all browsers.
// 3. Correct the line height in all browsers.
// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.
// 5. Change the default tap highlight to be completely transparent in iOS.
*,
*::before,
*::after {
box-sizing: border-box; // 1
}
html {
font-family: sans-serif; // 2
line-height: 1.15; // 3
-webkit-text-size-adjust: 100%; // 4
-webkit-tap-highlight-color: rgba($black, 0); // 5
}
// Shim for "new" HTML5 structural elements to display correctly (IE10, older browsers)
// TODO: remove in v5
// stylelint-disable-next-line selector-list-comma-newline-after
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
// Body
//
// 1. Remove the margin in all browsers.
// 2. As a best practice, apply a default `background-color`.
// 3. Set an explicit initial text-align value so that we can later use
// the `inherit` value on things like `<th>` elements.
body {
margin: 0; // 1
font-family: $font-family-base;
@include font-size($font-size-base);
font-weight: $font-weight-base;
line-height: $line-height-base;
color: $body-color;
text-align: left; // 3
background-color: $body-bg; // 2
}
// Future-proof rule: in browsers that support :focus-visible, suppress the focus outline
// on elements that programmatically receive focus but wouldn't normally show a visible
// focus outline. In general, this would mean that the outline is only applied if the
// interaction that led to the element receiving programmatic focus was a keyboard interaction,
// or the browser has somehow determined that the user is primarily a keyboard user and/or
// wants focus outlines to always be presented.
//
// See https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible
// and https://developer.paciellogroup.com/blog/2018/03/focus-visible-and-backwards-compatibility/
[tabindex="-1"]:focus:not(:focus-visible) {
outline: 0 !important;
}
// Content grouping
//
// 1. Add the correct box sizing in Firefox.
// 2. Show the overflow in Edge and IE.
hr {
box-sizing: content-box; // 1
height: 0; // 1
overflow: visible; // 2
}
//
// Typography
//
// Remove top margins from headings
//
// By default, `<h1>`-`<h6>` all receive top and bottom margins. We nuke the top
// margin for easier control within type scales as it avoids margin collapsing.
// stylelint-disable-next-line selector-list-comma-newline-after
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: $headings-margin-bottom;
}
// Reset margins on paragraphs
//
// Similarly, the top margin on `<p>`s get reset. However, we also reset the
// bottom margin to use `rem` units instead of `em`.
p {
margin-top: 0;
margin-bottom: $paragraph-margin-bottom;
}
// Abbreviations
//
// 1. Duplicate behavior to the data-* attribute for our tooltip plugin
// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
// 3. Add explicit cursor to indicate changed behavior.
// 4. Remove the bottom border in Firefox 39-.
// 5. Prevent the text-decoration to be skipped.
abbr[title],
abbr[data-original-title] { // 1
text-decoration: underline; // 2
text-decoration: underline dotted; // 2
cursor: help; // 3
border-bottom: 0; // 4
text-decoration-skip-ink: none; // 5
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: $dt-font-weight;
}
dd {
margin-bottom: .5rem;
margin-left: 0; // Undo browser default
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: $font-weight-bolder; // Add the correct font weight in Chrome, Edge, and Safari
}
small {
@include font-size(80%); // Add the correct font size in all browsers
}
//
// Prevent `sub` and `sup` elements from affecting the line height in
// all browsers.
//
sub,
sup {
position: relative;
@include font-size(75%);
line-height: 0;
vertical-align: baseline;
}
sub { bottom: -.25em; }
sup { top: -.5em; }
//
// Links
//
a {
color: $link-color;
text-decoration: $link-decoration;
background-color: transparent; // Remove the gray background on active links in IE 10.
@include hover() {
color: $link-hover-color;
text-decoration: $link-hover-decoration;
}
}
// And undo these styles for placeholder links/named anchors (without href).
// It would be more straightforward to just use a[href] in previous block, but that
// causes specificity issues in many other styles that are too complex to fix.
// See https://github.com/twbs/bootstrap/issues/19402
a:not([href]):not([class]) {
color: inherit;
text-decoration: none;
@include hover() {
color: inherit;
text-decoration: none;
}
}
//
// Code
//
pre,
code,
kbd,
samp {
font-family: $font-family-monospace;
@include font-size(1em); // Correct the odd `em` font sizing in all browsers.
}
pre {
// Remove browser default top margin
margin-top: 0;
// Reset browser default of `1em` to use `rem`s
margin-bottom: 1rem;
// Don't allow content to break outside
overflow: auto;
// Disable auto-hiding scrollbar in IE & legacy Edge to avoid overlap,
// making it impossible to interact with the content
-ms-overflow-style: scrollbar;
}
//
// Figures
//
figure {
// Apply a consistent margin strategy (matches our type styles).
margin: 0 0 1rem;
}
//
// Images and content
//
img {
vertical-align: middle;
border-style: none; // Remove the border on images inside links in IE 10-.
}
svg {
// Workaround for the SVG overflow bug in IE10/11 is still required.
// See https://github.com/twbs/bootstrap/issues/26878
overflow: hidden;
vertical-align: middle;
}
//
// Tables
//
table {
border-collapse: collapse; // Prevent double borders
}
caption {
padding-top: $table-cell-padding;
padding-bottom: $table-cell-padding;
color: $table-caption-color;
text-align: left;
caption-side: bottom;
}
// 1. Removes font-weight bold by inheriting
// 2. Matches default `<td>` alignment by inheriting `text-align`.
// 3. Fix alignment for Safari
th {
font-weight: $table-th-font-weight; // 1
text-align: inherit; // 2
text-align: -webkit-match-parent; // 3
}
//
// Forms
//
label {
// Allow labels to use `margin` for spacing.
display: inline-block;
margin-bottom: $label-margin-bottom;
}
// Remove the default `border-radius` that macOS Chrome adds.
//
// Details at https://github.com/twbs/bootstrap/issues/24093
button {
// stylelint-disable-next-line property-disallowed-list
border-radius: 0;
}
// Work around a Firefox/IE bug where the transparent `button` background
// results in a loss of the default `button` focus styles.
//
// Credit: https://github.com/suitcss/base/
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0; // Remove the margin in Firefox and Safari
font-family: inherit;
@include font-size(inherit);
line-height: inherit;
}
button,
input {
overflow: visible; // Show the overflow in Edge
}
button,
select {
text-transform: none; // Remove the inheritance of text transform in Firefox
}
// Set the cursor for non-`<button>` buttons
//
// Details at https://github.com/twbs/bootstrap/pull/30562
[role="button"] {
cursor: pointer;
}
// Remove the inheritance of word-wrap in Safari.
//
// Details at https://github.com/twbs/bootstrap/issues/24990
select {
word-wrap: normal;
}
// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
// controls in Android 4.
// 2. Correct the inability to style clickable types in iOS and Safari.
button,
[type="button"], // 1
[type="reset"],
[type="submit"] {
-webkit-appearance: button; // 2
}
// Opinionated: add "hand" cursor to non-disabled button elements.
@if $enable-pointer-cursor-for-buttons {
button,
[type="button"],
[type="reset"],
[type="submit"] {
&:not(:disabled) {
cursor: pointer;
}
}
}
// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box; // 1. Add the correct box sizing in IE 10-
padding: 0; // 2. Remove the padding in IE 10-
}
textarea {
overflow: auto; // Remove the default vertical scrollbar in IE.
// Textareas should really only resize vertically so they don't break their (horizontal) containers.
resize: vertical;
}
fieldset {
// Browsers set a default `min-width: min-content;` on fieldsets,
// unlike e.g. `<div>`s, which have `min-width: 0;` by default.
// So we reset that to ensure fieldsets behave more like a standard block element.
// See https://github.com/twbs/bootstrap/issues/12359
// and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements
min-width: 0;
// Reset the default outline behavior of fieldsets so they don't affect page layout.
padding: 0;
margin: 0;
border: 0;
}
// 1. Correct the text wrapping in Edge and IE.
// 2. Correct the color inheritance from `fieldset` elements in IE.
legend {
display: block;
width: 100%;
max-width: 100%; // 1
padding: 0;
margin-bottom: .5rem;
@include font-size(1.5rem);
line-height: inherit;
color: inherit; // 2
white-space: normal; // 1
}
progress {
vertical-align: baseline; // Add the correct vertical alignment in Chrome, Firefox, and Opera.
}
// Correct the cursor style of increment and decrement buttons in Chrome.
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
// This overrides the extra rounded corners on search inputs in iOS so that our
// `.form-control` class can properly style them. Note that this cannot simply
// be added to `.form-control` as it's not specific enough. For details, see
// https://github.com/twbs/bootstrap/issues/11586.
outline-offset: -2px; // 2. Correct the outline style in Safari.
-webkit-appearance: none;
}
//
// Remove the inner padding in Chrome and Safari on macOS.
//
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
//
// 1. Correct the inability to style clickable types in iOS and Safari.
// 2. Change font properties to `inherit` in Safari.
//
::-webkit-file-upload-button {
font: inherit; // 2
-webkit-appearance: button; // 1
}
//
// Correct element displays
//
output {
display: inline-block;
}
summary {
display: list-item; // Add the correct display in all browsers
cursor: pointer;
}
template {
display: none; // Add the correct display in IE
}
// Always hide an element with the `hidden` HTML attribute (from PureCSS).
// Needed for proper display in IE 10-.
[hidden] {
display: none !important;
}

View File

@@ -0,0 +1,56 @@
//
// Rotating border
//
@keyframes spinner-border {
to { transform: rotate(360deg); }
}
.spinner-border {
display: inline-block;
width: $spinner-width;
height: $spinner-height;
vertical-align: text-bottom;
border: $spinner-border-width solid currentColor;
border-right-color: transparent;
// stylelint-disable-next-line property-disallowed-list
border-radius: 50%;
animation: spinner-border .75s linear infinite;
}
.spinner-border-sm {
width: $spinner-width-sm;
height: $spinner-height-sm;
border-width: $spinner-border-width-sm;
}
//
// Growing circle
//
@keyframes spinner-grow {
0% {
transform: scale(0);
}
50% {
opacity: 1;
transform: none;
}
}
.spinner-grow {
display: inline-block;
width: $spinner-width;
height: $spinner-height;
vertical-align: text-bottom;
background-color: currentColor;
// stylelint-disable-next-line property-disallowed-list
border-radius: 50%;
opacity: 0;
animation: spinner-grow .75s linear infinite;
}
.spinner-grow-sm {
width: $spinner-width-sm;
height: $spinner-height-sm;
}

View File

@@ -0,0 +1,185 @@
//
// Basic Bootstrap table
//
.table {
width: 100%;
margin-bottom: $spacer;
color: $table-color;
background-color: $table-bg; // Reset for nesting within parents with `background-color`.
th,
td {
padding: $table-cell-padding;
vertical-align: top;
border-top: $table-border-width solid $table-border-color;
}
thead th {
vertical-align: bottom;
border-bottom: (2 * $table-border-width) solid $table-border-color;
}
tbody + tbody {
border-top: (2 * $table-border-width) solid $table-border-color;
}
}
//
// Condensed table w/ half padding
//
.table-sm {
th,
td {
padding: $table-cell-padding-sm;
}
}
// Border versions
//
// Add or remove borders all around the table and between all the columns.
.table-bordered {
border: $table-border-width solid $table-border-color;
th,
td {
border: $table-border-width solid $table-border-color;
}
thead {
th,
td {
border-bottom-width: 2 * $table-border-width;
}
}
}
.table-borderless {
th,
td,
thead th,
tbody + tbody {
border: 0;
}
}
// Zebra-striping
//
// Default zebra-stripe styles (alternating gray and transparent backgrounds)
.table-striped {
tbody tr:nth-of-type(#{$table-striped-order}) {
background-color: $table-accent-bg;
}
}
// Hover effect
//
// Placed here since it has to come after the potential zebra striping
.table-hover {
tbody tr {
@include hover() {
color: $table-hover-color;
background-color: $table-hover-bg;
}
}
}
// Table backgrounds
//
// Exact selectors below required to override `.table-striped` and prevent
// inheritance to nested tables.
@each $color, $value in $theme-colors {
@include table-row-variant($color, theme-color-level($color, $table-bg-level), theme-color-level($color, $table-border-level));
}
@include table-row-variant(active, $table-active-bg);
// Dark styles
//
// Same table markup, but inverted color scheme: dark background and light text.
// stylelint-disable-next-line no-duplicate-selectors
.table {
.thead-dark {
th {
color: $table-dark-color;
background-color: $table-dark-bg;
border-color: $table-dark-border-color;
}
}
.thead-light {
th {
color: $table-head-color;
background-color: $table-head-bg;
border-color: $table-border-color;
}
}
}
.table-dark {
color: $table-dark-color;
background-color: $table-dark-bg;
th,
td,
thead th {
border-color: $table-dark-border-color;
}
&.table-bordered {
border: 0;
}
&.table-striped {
tbody tr:nth-of-type(#{$table-striped-order}) {
background-color: $table-dark-accent-bg;
}
}
&.table-hover {
tbody tr {
@include hover() {
color: $table-dark-hover-color;
background-color: $table-dark-hover-bg;
}
}
}
}
// Responsive tables
//
// Generate series of `.table-responsive-*` classes for configuring the screen
// size of where your table will overflow.
.table-responsive {
@each $breakpoint in map-keys($grid-breakpoints) {
$next: breakpoint-next($breakpoint, $grid-breakpoints);
$infix: breakpoint-infix($next, $grid-breakpoints);
&#{$infix} {
@include media-breakpoint-down($breakpoint) {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
// Prevent double border on horizontal scroll due to use of `display: block;`
> .table-bordered {
border: 0;
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
.toast {
// Prevents from shrinking in IE11, when in a flex container
// See https://github.com/twbs/bootstrap/issues/28341
flex-basis: $toast-max-width;
max-width: $toast-max-width;
@include font-size($toast-font-size);
color: $toast-color;
background-color: $toast-background-color;
background-clip: padding-box;
border: $toast-border-width solid $toast-border-color;
box-shadow: $toast-box-shadow;
opacity: 0;
@include border-radius($toast-border-radius);
&:not(:last-child) {
margin-bottom: $toast-padding-x;
}
&.showing {
opacity: 1;
}
&.show {
display: block;
opacity: 1;
}
&.hide {
display: none;
}
}
.toast-header {
display: flex;
align-items: center;
padding: $toast-padding-y $toast-padding-x;
color: $toast-header-color;
background-color: $toast-header-background-color;
background-clip: padding-box;
border-bottom: $toast-border-width solid $toast-header-border-color;
@include border-top-radius(subtract($toast-border-radius, $toast-border-width));
}
.toast-body {
padding: $toast-padding-x; // apply to both vertical and horizontal
}

View File

@@ -0,0 +1,115 @@
// Base class
.tooltip {
position: absolute;
z-index: $zindex-tooltip;
display: block;
margin: $tooltip-margin;
// Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.
// So reset our font and text properties to avoid inheriting weird values.
@include reset-text();
@include font-size($tooltip-font-size);
// Allow breaking very long words so they don't overflow the tooltip's bounds
word-wrap: break-word;
opacity: 0;
&.show { opacity: $tooltip-opacity; }
.arrow {
position: absolute;
display: block;
width: $tooltip-arrow-width;
height: $tooltip-arrow-height;
&::before {
position: absolute;
content: "";
border-color: transparent;
border-style: solid;
}
}
}
.bs-tooltip-top {
padding: $tooltip-arrow-height 0;
.arrow {
bottom: 0;
&::before {
top: 0;
border-width: $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;
border-top-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-right {
padding: 0 $tooltip-arrow-height;
.arrow {
left: 0;
width: $tooltip-arrow-height;
height: $tooltip-arrow-width;
&::before {
right: 0;
border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;
border-right-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-bottom {
padding: $tooltip-arrow-height 0;
.arrow {
top: 0;
&::before {
bottom: 0;
border-width: 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;
border-bottom-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-left {
padding: 0 $tooltip-arrow-height;
.arrow {
right: 0;
width: $tooltip-arrow-height;
height: $tooltip-arrow-width;
&::before {
left: 0;
border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;
border-left-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-auto {
&[x-placement^="top"] {
@extend .bs-tooltip-top;
}
&[x-placement^="right"] {
@extend .bs-tooltip-right;
}
&[x-placement^="bottom"] {
@extend .bs-tooltip-bottom;
}
&[x-placement^="left"] {
@extend .bs-tooltip-left;
}
}
// Wrapper for the tooltip content
.tooltip-inner {
max-width: $tooltip-max-width;
padding: $tooltip-padding-y $tooltip-padding-x;
color: $tooltip-color;
text-align: center;
background-color: $tooltip-bg;
@include border-radius($tooltip-border-radius);
}

View File

@@ -0,0 +1,20 @@
.fade {
@include transition($transition-fade);
&:not(.show) {
opacity: 0;
}
}
.collapse {
&:not(.show) {
display: none;
}
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
@include transition($transition-collapse);
}

View File

@@ -0,0 +1,125 @@
// stylelint-disable declaration-no-important, selector-list-comma-newline-after
//
// Headings
//
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
margin-bottom: $headings-margin-bottom;
font-family: $headings-font-family;
font-weight: $headings-font-weight;
line-height: $headings-line-height;
color: $headings-color;
}
h1, .h1 { @include font-size($h1-font-size); }
h2, .h2 { @include font-size($h2-font-size); }
h3, .h3 { @include font-size($h3-font-size); }
h4, .h4 { @include font-size($h4-font-size); }
h5, .h5 { @include font-size($h5-font-size); }
h6, .h6 { @include font-size($h6-font-size); }
.lead {
@include font-size($lead-font-size);
font-weight: $lead-font-weight;
}
// Type display classes
.display-1 {
@include font-size($display1-size);
font-weight: $display1-weight;
line-height: $display-line-height;
}
.display-2 {
@include font-size($display2-size);
font-weight: $display2-weight;
line-height: $display-line-height;
}
.display-3 {
@include font-size($display3-size);
font-weight: $display3-weight;
line-height: $display-line-height;
}
.display-4 {
@include font-size($display4-size);
font-weight: $display4-weight;
line-height: $display-line-height;
}
//
// Horizontal rules
//
hr {
margin-top: $hr-margin-y;
margin-bottom: $hr-margin-y;
border: 0;
border-top: $hr-border-width solid $hr-border-color;
}
//
// Emphasis
//
small,
.small {
@include font-size($small-font-size);
font-weight: $font-weight-normal;
}
mark,
.mark {
padding: $mark-padding;
background-color: $mark-bg;
}
//
// Lists
//
.list-unstyled {
@include list-unstyled();
}
// Inline turns list items into inline-block
.list-inline {
@include list-unstyled();
}
.list-inline-item {
display: inline-block;
&:not(:last-child) {
margin-right: $list-inline-padding;
}
}
//
// Misc
//
// Builds on `abbr`
.initialism {
@include font-size(90%);
text-transform: uppercase;
}
// Blockquotes
.blockquote {
margin-bottom: $spacer;
@include font-size($blockquote-font-size);
}
.blockquote-footer {
display: block;
@include font-size($blockquote-small-font-size);
color: $blockquote-small-color;
&::before {
content: "\2014\00A0"; // em dash, nbsp
}
}

View File

@@ -0,0 +1,505 @@
// stylelint-disable declaration-no-important
.align-baseline { vertical-align: baseline !important; } // Browser default
.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; }
// Background
// stylelint-disable declaration-no-important
@each $color, $value in $theme-colors {
@include bg-variant(".bg-#{$color}", $value, true);
}
@if $enable-gradients {
@each $color, $value in $theme-colors {
@include bg-gradient-variant(".bg-gradient-#{$color}", $value, true);
}
}
.bg-white {
background-color: $white !important;
}
.bg-transparent {
background-color: transparent !important;
}
// stylelint-disable property-disallowed-list, declaration-no-important
//
// Border
//
.border { border: $border-width solid $border-color !important; }
.border-top { border-top: $border-width solid $border-color !important; }
.border-right { border-right: $border-width solid $border-color !important; }
.border-bottom { border-bottom: $border-width solid $border-color !important; }
.border-left { border-left: $border-width solid $border-color !important; }
.border-0 { border: 0 !important; }
.border-top-0 { border-top: 0 !important; }
.border-right-0 { border-right: 0 !important; }
.border-bottom-0 { border-bottom: 0 !important; }
.border-left-0 { border-left: 0 !important; }
@each $color, $value in $theme-colors {
.border-#{$color} {
border-color: $value !important;
}
}
.border-white {
border-color: $white !important;
}
//
// Border-radius
//
.rounded-sm {
border-radius: $border-radius-sm !important;
}
.rounded {
border-radius: $border-radius !important;
}
.rounded-top {
border-top-left-radius: $border-radius !important;
border-top-right-radius: $border-radius !important;
}
.rounded-right {
border-top-right-radius: $border-radius !important;
border-bottom-right-radius: $border-radius !important;
}
.rounded-bottom {
border-bottom-right-radius: $border-radius !important;
border-bottom-left-radius: $border-radius !important;
}
.rounded-left {
border-top-left-radius: $border-radius !important;
border-bottom-left-radius: $border-radius !important;
}
.rounded-lg {
border-radius: $border-radius-lg !important;
}
.rounded-circle {
border-radius: 50% !important;
}
.rounded-pill {
border-radius: $rounded-pill !important;
}
.rounded-0 {
border-radius: 0 !important;
}
.clearfix {
@include clearfix();
}
// stylelint-disable declaration-no-important
//
// Utilities for common `display` values
//
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
@each $value in $displays {
.d#{$infix}-#{$value} { display: $value !important; }
}
}
}
//
// Utilities for toggling `display` in print
//
@media print {
@each $value in $displays {
.d-print-#{$value} { display: $value !important; }
}
}
// Credit: Nicolas Gallagher and SUIT CSS.
.embed-responsive {
position: relative;
display: block;
width: 100%;
padding: 0;
overflow: hidden;
&::before {
display: block;
content: "";
}
.embed-responsive-item,
iframe,
embed,
object,
video {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
}
@each $embed-responsive-aspect-ratio in $embed-responsive-aspect-ratios {
$embed-responsive-aspect-ratio-x: nth($embed-responsive-aspect-ratio, 1);
$embed-responsive-aspect-ratio-y: nth($embed-responsive-aspect-ratio, 2);
.embed-responsive-#{$embed-responsive-aspect-ratio-x}by#{$embed-responsive-aspect-ratio-y} {
&::before {
padding-top: percentage($embed-responsive-aspect-ratio-y / $embed-responsive-aspect-ratio-x);
}
}
}
// stylelint-disable declaration-no-important
// Flex variation
//
// Custom styles for additional flex alignment options.
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.flex#{$infix}-row { flex-direction: row !important; }
.flex#{$infix}-column { flex-direction: column !important; }
.flex#{$infix}-row-reverse { flex-direction: row-reverse !important; }
.flex#{$infix}-column-reverse { flex-direction: column-reverse !important; }
.flex#{$infix}-wrap { flex-wrap: wrap !important; }
.flex#{$infix}-nowrap { flex-wrap: nowrap !important; }
.flex#{$infix}-wrap-reverse { flex-wrap: wrap-reverse !important; }
.flex#{$infix}-fill { flex: 1 1 auto !important; }
.flex#{$infix}-grow-0 { flex-grow: 0 !important; }
.flex#{$infix}-grow-1 { flex-grow: 1 !important; }
.flex#{$infix}-shrink-0 { flex-shrink: 0 !important; }
.flex#{$infix}-shrink-1 { flex-shrink: 1 !important; }
.justify-content#{$infix}-start { justify-content: flex-start !important; }
.justify-content#{$infix}-end { justify-content: flex-end !important; }
.justify-content#{$infix}-center { justify-content: center !important; }
.justify-content#{$infix}-between { justify-content: space-between !important; }
.justify-content#{$infix}-around { justify-content: space-around !important; }
.align-items#{$infix}-start { align-items: flex-start !important; }
.align-items#{$infix}-end { align-items: flex-end !important; }
.align-items#{$infix}-center { align-items: center !important; }
.align-items#{$infix}-baseline { align-items: baseline !important; }
.align-items#{$infix}-stretch { align-items: stretch !important; }
.align-content#{$infix}-start { align-content: flex-start !important; }
.align-content#{$infix}-end { align-content: flex-end !important; }
.align-content#{$infix}-center { align-content: center !important; }
.align-content#{$infix}-between { align-content: space-between !important; }
.align-content#{$infix}-around { align-content: space-around !important; }
.align-content#{$infix}-stretch { align-content: stretch !important; }
.align-self#{$infix}-auto { align-self: auto !important; }
.align-self#{$infix}-start { align-self: flex-start !important; }
.align-self#{$infix}-end { align-self: flex-end !important; }
.align-self#{$infix}-center { align-self: center !important; }
.align-self#{$infix}-baseline { align-self: baseline !important; }
.align-self#{$infix}-stretch { align-self: stretch !important; }
}
}
// stylelint-disable declaration-no-important
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.float#{$infix}-left { float: left !important; }
.float#{$infix}-right { float: right !important; }
.float#{$infix}-none { float: none !important; }
}
}
// stylelint-disable declaration-no-important
@each $value in $user-selects {
.user-select-#{$value} { user-select: $value !important; }
}
// stylelint-disable declaration-no-important
@each $value in $overflows {
.overflow-#{$value} { overflow: $value !important; }
}
// stylelint-disable declaration-no-important
// Common values
@each $position in $positions {
.position-#{$position} { position: $position !important; }
}
// Shorthand
.fixed-top {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: $zindex-fixed;
}
.fixed-bottom {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: $zindex-fixed;
}
.sticky-top {
@supports (position: sticky) {
position: sticky;
top: 0;
z-index: $zindex-sticky;
}
}
//
// Screenreaders
//
.sr-only {
@include sr-only();
}
.sr-only-focusable {
@include sr-only-focusable();
}
// stylelint-disable declaration-no-important
.shadow-sm { box-shadow: $box-shadow-sm !important; }
.shadow { box-shadow: $box-shadow !important; }
.shadow-lg { box-shadow: $box-shadow-lg !important; }
.shadow-none { box-shadow: none !important; }
// stylelint-disable declaration-no-important
// Width and height
@each $prop, $abbrev in (width: w, height: h) {
@each $size, $length in $sizes {
.#{$abbrev}-#{$size} { #{$prop}: $length !important; }
}
}
.mw-100 { max-width: 100% !important; }
.mh-100 { max-height: 100% !important; }
// Viewport additional helpers
.min-vw-100 { min-width: 100vw !important; }
.min-vh-100 { min-height: 100vh !important; }
.vw-100 { width: 100vw !important; }
.vh-100 { height: 100vh !important; }
// stylelint-disable declaration-no-important
// Margin and Padding
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
@each $prop, $abbrev in (margin: m, padding: p) {
@each $size, $length in $spacers {
.#{$abbrev}#{$infix}-#{$size} { #{$prop}: $length !important; }
.#{$abbrev}t#{$infix}-#{$size},
.#{$abbrev}y#{$infix}-#{$size} {
#{$prop}-top: $length !important;
}
.#{$abbrev}r#{$infix}-#{$size},
.#{$abbrev}x#{$infix}-#{$size} {
#{$prop}-right: $length !important;
}
.#{$abbrev}b#{$infix}-#{$size},
.#{$abbrev}y#{$infix}-#{$size} {
#{$prop}-bottom: $length !important;
}
.#{$abbrev}l#{$infix}-#{$size},
.#{$abbrev}x#{$infix}-#{$size} {
#{$prop}-left: $length !important;
}
}
}
// Negative margins (e.g., where `.mb-n1` is negative version of `.mb-1`)
@each $size, $length in $spacers {
@if $size != 0 {
.m#{$infix}-n#{$size} { margin: -$length !important; }
.mt#{$infix}-n#{$size},
.my#{$infix}-n#{$size} {
margin-top: -$length !important;
}
.mr#{$infix}-n#{$size},
.mx#{$infix}-n#{$size} {
margin-right: -$length !important;
}
.mb#{$infix}-n#{$size},
.my#{$infix}-n#{$size} {
margin-bottom: -$length !important;
}
.ml#{$infix}-n#{$size},
.mx#{$infix}-n#{$size} {
margin-left: -$length !important;
}
}
}
// Some special margin utils
.m#{$infix}-auto { margin: auto !important; }
.mt#{$infix}-auto,
.my#{$infix}-auto {
margin-top: auto !important;
}
.mr#{$infix}-auto,
.mx#{$infix}-auto {
margin-right: auto !important;
}
.mb#{$infix}-auto,
.my#{$infix}-auto {
margin-bottom: auto !important;
}
.ml#{$infix}-auto,
.mx#{$infix}-auto {
margin-left: auto !important;
}
}
}
//
// Stretched link
//
.stretched-link {
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
// Just in case `pointer-events: none` is set on a parent
pointer-events: auto;
content: "";
// IE10 bugfix, see https://stackoverflow.com/questions/16947967/ie10-hover-pseudo-class-doesnt-work-without-background-color
background-color: rgba(0, 0, 0, 0);
}
}
// stylelint-disable declaration-no-important
//
// Text
//
.text-monospace { font-family: $font-family-monospace !important; }
// Alignment
.text-justify { text-align: justify !important; }
.text-wrap { white-space: normal !important; }
.text-nowrap { white-space: nowrap !important; }
.text-truncate { @include text-truncate(); }
// Responsive alignment
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.text#{$infix}-left { text-align: left !important; }
.text#{$infix}-right { text-align: right !important; }
.text#{$infix}-center { text-align: center !important; }
}
}
// Transformation
.text-lowercase { text-transform: lowercase !important; }
.text-uppercase { text-transform: uppercase !important; }
.text-capitalize { text-transform: capitalize !important; }
// Weight and italics
.font-weight-light { font-weight: $font-weight-light !important; }
.font-weight-lighter { font-weight: $font-weight-lighter !important; }
.font-weight-normal { font-weight: $font-weight-normal !important; }
.font-weight-bold { font-weight: $font-weight-bold !important; }
.font-weight-bolder { font-weight: $font-weight-bolder !important; }
.font-italic { font-style: italic !important; }
// Contextual colors
.text-white { color: $white !important; }
@each $color, $value in $theme-colors {
@include text-emphasis-variant(".text-#{$color}", $value, true);
}
.text-body { color: $body-color !important; }
.text-muted { color: $text-muted !important; }
.text-black-50 { color: rgba($black, .5) !important; }
.text-white-50 { color: rgba($white, .5) !important; }
// Misc
.text-hide {
@include text-hide($ignore-warning: true);
}
.text-decoration-none { text-decoration: none !important; }
.text-break {
word-break: break-word !important; // Deprecated, but avoids issues with flex containers
word-wrap: break-word !important; // Used instead of `overflow-wrap` for IE & Edge Legacy
}
// Reset
.text-reset { color: inherit !important; }
// stylelint-disable declaration-no-important
//
// Visibility utilities
//
.visible {
visibility: visible !important;
}
.invisible {
visibility: hidden !important;
}

View File

@@ -0,0 +1,140 @@
// stylelint-disable declaration-no-important, selector-no-qualifying-type
// Source: https://github.com/h5bp/main.css/blob/master/src/_print.css
// ==========================================================================
// Print styles.
// Inlined to avoid the additional HTTP request:
// https://www.phpied.com/delay-loading-your-print-css/
// ==========================================================================
@if $enable-print-styles {
@media print {
*,
*::before,
*::after {
// Bootstrap specific; comment out `color` and `background`
//color: $black !important; // Black prints faster
text-shadow: none !important;
//background: transparent !important;
box-shadow: none !important;
}
a {
&:not(.btn) {
text-decoration: underline;
}
}
// Bootstrap specific; comment the following selector out
//a[href]::after {
// content: " (" attr(href) ")";
//}
abbr[title]::after {
content: " (" attr(title) ")";
}
// Bootstrap specific; comment the following selector out
//
// Don't show links that are fragment identifiers,
// or use the `javascript:` pseudo protocol
//
//a[href^="#"]::after,
//a[href^="javascript:"]::after {
// content: "";
//}
pre {
white-space: pre-wrap !important;
}
pre,
blockquote {
border: $border-width solid $gray-500; // Bootstrap custom code; using `$border-width` instead of 1px
page-break-inside: avoid;
}
//
// Printing Tables:
// https://web.archive.org/web/20180815150934/http://css-discuss.incutio.com/wiki/Printing_Tables
//
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
// Bootstrap specific changes start
// Specify a size and min-width to make printing closer across browsers.
// We don't set margin here because it breaks `size` in Chrome. We also
// don't use `!important` on `size` as it breaks in Chrome.
@page {
size: $print-page-size;
}
body {
min-width: $print-body-min-width !important;
}
.container {
min-width: $print-body-min-width !important;
}
// Bootstrap components
.navbar {
display: none;
}
.badge {
border: $border-width solid $black;
}
.table {
border-collapse: collapse !important;
td,
th {
background-color: $white !important;
}
}
.table-bordered {
th,
td {
border: 1px solid $gray-300 !important;
}
}
.table-dark {
color: inherit;
th,
td,
thead th,
tbody + tbody {
border-color: $table-border-color;
}
}
.table .thead-dark th {
color: inherit;
border-color: $table-border-color;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,521 @@
html {
height: 100vh;
width: 100vw;
overflow: hidden;
}
body {
height: 100%;
}
.pointer {
cursor: pointer;
}
.grab {
cursor: grab;
}
.gap {
gap: 0.25rem;
}
.gap-0 {
gap: 0;
}
.gap-1 {
gap: 0.5rem;
}
.gap-2 {
gap: 1rem;
}
.gap-3 {
gap: 1.5rem;
}
@media (max-width: 576px) {
.flex-fill-child>* {
flex: 1 1 auto !important;
}
}
:disabled,
.disabled {
cursor: not-allowed !important;
pointer-events: auto !important;
}
thead th,
legend,
label {
font-family: var(--font-medium);
}
strong,
b,
.font-weight-bold {
font-family: var(--font-semibold);
}
a.btn:not(.btn-link) {
text-decoration: none;
}
.tabs>.card-header {
border-bottom: 1px solid var(--extra-light) !important;
padding-top: 0 !important;
padding-left: 0.8rem !important;
padding-right: 0.8rem !important;
.card-header-tabs {
border-bottom: 0 !important;
}
}
.card-header {
.nav-item {
font-family: var(--font-medium) !important;
margin-bottom: 0 !important;
.nav-link {
padding: 0.5rem !important;
&:not(.active):not(.disabled) {
color: var(--dark) !important;
}
}
}
}
.b-form-tag {
align-items: center !important;
}
// Used for back button
.back {
&:hover {
text-decoration: none;
.back-icon {
transition: transform 0.3s ease-out;
transform: translateX(-4px);
}
}
}
[dir="rtl"] {
.back {
.back-icon {
display: none;
}
}
}
.table.b-table {
th {
white-space: nowrap;
}
tr {
&:first-child td {
border: none;
}
th {
border: 0;
}
}
}
fieldset.required {
legend>div:first-child>span:first-child::after {
content: "*";
color: var(--danger);
}
}
// Padding because of toolbar
.b-toaster.b-toaster-bottom-right {
bottom: 75px;
}
// Fixes dropdowns in scrollable modals
.modal-dialog-scrollable .modal-content {
overflow: initial;
}
.modal-content {
border-radius: 1rem;
}
.dropdown-menu * {
font-family: var(--font-regular) !important;
.dropdown-item {
outline: none !important;
* {
border: 0 !important;
box-shadow: none !important;
background-color: transparent !important;
}
&:active * {
color: var(--white) !important;
}
}
}
.vue-grid-layout {
transition: none !important;
}
.vue-grid-item {
transition: none !important;
}
::-webkit-scrollbar {
width: 14px;
height: 14px;
}
::-webkit-scrollbar-track {
border-radius: 6px;
}
::-webkit-scrollbar-thumb {
background: var(--secondary);
background-clip: content-box;
border: 3px solid transparent;
border-radius: 7px;
&:hover {
background-color: var(--primary);
}
}
::-webkit-scrollbar-corner {
background: transparent;
}
.sidebar-spacer {
min-width: 0px;
transition: min-width 0.15s ease-in-out;
@media (min-width: 1024px) {
&.expanded {
min-width: calc(var(--sidebar-width));
transition: min-width 0.15s ease-in-out;
}
}
}
// Supporting CSS to improve print-to-PDF option
@media print {
@page {
size: auto;
}
body {
margin: 0;
}
header,
footer,
aside,
nav {
display: none;
}
.block {
break-inside: avoid;
width: 100%;
}
}
// Rich text editor stuff
/* Basic editor styles */
.rt-content {
position: static;
:first-child {
margin-top: 0 !important;
}
p {
margin: 0 !important;
padding: 0 !important;
}
/* Headings */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Lists */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
ol {
margin-left: 0.1rem !important;
}
/* Blockquote */
blockquote {
border-left: 3px solid var(--light);
margin: 1.5rem 0;
padding-left: 1rem;
font-style: italic;
}
/* Code and preformatted text */
code {
background-color: var(--light);
border-radius: 0.4rem;
color: var(--dark);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--dark);
border-radius: 0.5rem;
color: var(--light);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
overflow-x: auto;
white-space: pre-wrap;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
/* Task list - official Tiptap structure */
ul[data-type='taskList'] {
list-style: none;
margin-left: 0;
padding: 0;
li {
align-items: flex-start;
display: flex;
margin-bottom: 0.25rem;
>label {
flex: 0 0 auto;
margin-bottom: 0 !important;
user-select: none;
}
>div {
flex: 1 1 auto;
}
}
input[type='checkbox'] {
pointer-events: none;
cursor: default;
width: 0.9rem;
height: 0.9rem;
margin: 0.175rem 0.5rem 0rem 0rem !important
}
ul[data-type='taskList'] {
margin: 0;
}
}
/* Links */
a {
color: var(--primary);
text-decoration: underline;
&:hover {
color: var(--dark);
}
}
/* Horizontal rule */
hr {
border: none;
border-top: 1px solid var(--light);
margin: 2rem 0;
}
/* Images */
img {
max-width: 100%;
height: auto;
border-radius: 6px;
margin: 1em 0;
}
/* Emphasis */
strong,
b {
font-weight: 600;
}
em,
i {
font-style: italic;
}
/* Strikethrough */
s,
del {
text-decoration: line-through;
}
/* Subscript and superscript */
sub,
sup {
font-size: 0.75em;
line-height: 0;
}
sub {
vertical-align: sub;
}
sup {
vertical-align: super;
}
/* Mark/highlight */
mark {
background-color: var(--primary);
color: var(--white);
padding: 0.1em 0.3em;
border-radius: 3px;
}
table {
border-collapse: collapse;
margin: 0;
overflow: hidden;
table-layout: fixed;
width: 100%;
td,
th {
border: 1px solid var(--extra-light);
box-sizing: border-box;
min-width: 1em;
padding: 6px 8px;
position: relative;
vertical-align: top;
>* {
margin-bottom: 0;
}
}
th {
background-color: var(--extra-light);
font-weight: bold;
text-align: left;
}
.selectedCell:after {
background: var(--light);
content: '';
left: 0;
right: 0;
top: 0;
bottom: 0;
pointer-events: none;
position: absolute;
z-index: 2;
}
.column-resize-handle {
background-color: var(--primary);
bottom: -2px;
pointer-events: none;
position: absolute;
right: -2px;
top: 0;
width: 4px;
}
}
.tableWrapper {
margin: 1.5rem 0;
overflow-x: auto;
}
.mention {
background-color: var(--extra-light);
border-radius: 0.5rem;
padding: 0rem 0.2rem;
box-decoration-break: clone;
color: var(--primary);
border: 1px solid var(--extra-light);
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
.tiptap p.is-editor-empty:first-child::before {
color: var(--secondary);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
}
.ProseMirror.ProseMirror-focused {
outline: none;
}

View File

@@ -0,0 +1,47 @@
// Do not forget to update getting-started/theming.md!
@if $theme-mode == "light" {
/* stylelint-disable */
// This is used within corteza-webapp-compose/src/lib/block/Calendar/feedLoader/
// to determine default event colors
:export {
primary: $primary;
danger: $danger;
secondary: $secondary;
}
/* stylelint-enable */
:root,
[data-color-mode="light"] {
// Custom variable values only support SassScript inside `#{}`.
@each $color, $value in $colors {
--#{$color}: #{$value};
}
@each $color, $value in $theme-colors {
--#{$color}: #{$value};
}
@each $bp, $value in $grid-breakpoints {
--breakpoint-#{$bp}: #{$value};
}
@each $variable, $value in $corteza-specific {
--#{$variable}: #{$value};
}
// Use `inspect` for lists so that quoted items keep the quotes.
// See https://github.com/sass/sass/issues/2383#issuecomment-336349172
--font-family-sans-serif: #{inspect($font-family-sans-serif)};
--font-family-monospace: #{inspect($font-family-monospace)};
}
} @else {
[data-color-mode="#{$theme-mode}"] {
@each $color, $value in $theme-colors {
--#{$color}: #{$value};
}
@each $variable, $value in $corteza-specific {
--#{$variable}: #{$value};
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,192 @@
//================================================
// Webapp Variables
//================================================
$wideminwidth: 767px !default;
$confortableminwidth: 1020px !default;
// Typography
$font-light: 'Poppins-Light' !default;
$font-regular: 'Poppins-Regular' !default;
$font-medium: 'Poppins-Medium' !default;
$font-semibold: 'Poppins-Semibold' !default;
$font-bold: 'Poppins-Bold' !default;
$font-size-base: 0.9rem !default;
$font-family-base: $font-regular !default;
$headings-font-family: $font-bold !default;
// Color system
$white: #FFFFFF !default;
$black: #0B344E !default;
$primary: #FF9661 !default;
$secondary: #758D9B !default;
$success: #43AA8B !default;
$warning: #E27646 !default;
$danger: #E54122 !default;
$light: #F3F5F7 !default;
$extra-light: #E4E9EF !default;
$dark: $black !default;
$gray-100: if(lightness($black) < lightness($white), mix($black, $white, 10%), mix($white, $black, 10%));
$gray-200: if(lightness($black) < lightness($white), mix($black, $white, 20%), mix($white, $black, 20%));
$gray-300: if(lightness($black) < lightness($white), mix($black, $white, 30%), mix($white, $black, 30%));
$gray-400: if(lightness($black) < lightness($white), mix($black, $white, 40%), mix($white, $black, 40%));
$gray-500: if(lightness($black) < lightness($white), mix($black, $white, 50%), mix($white, $black, 50%));
$gray-600: if(lightness($black) < lightness($white), mix($black, $white, 60%), mix($white, $black, 60%));
$gray-700: if(lightness($black) < lightness($white), mix($black, $white, 70%), mix($white, $black, 70%));
$gray-800: if(lightness($black) < lightness($white), mix($black, $white, 80%), mix($white, $black, 80%));
$gray-900: if(lightness($black) < lightness($white), mix($black, $white, 90%), mix($white, $black, 90%));
// Adjusting YIQ contrast settings for better text contrast
$yiq-contrasted-threshold: 180;
$yiq-text-dark: if(lightness($black) < lightness($white), $black, $white);
$yiq-text-light: if(lightness($black) < lightness($white), $white, $black);
$theme-colors: () !default;
$theme-colors: map-merge(
(
"white": $white,
"black": $black,
"extra-light": $extra-light,
),
$theme-colors
);
// Grid
$grid-gutter-width: 32px !default;
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px,
) !default;
// Body
$body-bg: $light !default;
$body-color: $black !default;
$border-color: $extra-light !default;
$hr-border-color: $border-color !default;
// Options
$enable-rounded: true !default;
$enable-gradients: false !default;
$enable-responsive-font-sizes: true !default;
// Card
$card-spacer-y: 1rem !default;
$card-spacer-x: 1rem !default;
$card-border-color: $extra-light !default;
$card-border-radius: 1rem !default;
$card-border-width: 0 !default;
$card-cap-bg: $white !default;
// Tables
$table-cell-padding-sm: 0.25rem !default;
$table-color: $black !default;
$table-head-color: $black !default;
$table-head-bg: $light !default;
$table-hover-bg: $light !default;
$table-border-color: $extra-light !default;
// Buttons
$btn-font-size: $font-size-base !default;
$btn-padding-y: 0.25em !default;
$btn-padding-x: 1em !default;
$input-btn-border-width: 1px !default;
$input-btn-focus-width: 2px !default;
$btn-focus-box-shadow: none !default;
$btn-active-box-shadow: none !default;
$btn-font-size-lg: 16px !default;
$btn-font-family: $font-medium !default;
$list-group-hover-bg: $light !default;
$list-group-action-color: $black !default;
$list-group-action-active-bg: $primary !default;
$list-group-action-active-color: $black !default;
// Tabs
$nav-link-color: $black !default;
$nav-tabs-border-color: transparent !default;
$nav-tabs-border-width: 4px !default;
$nav-tabs-border-radius: 0 !default;
$nav-tabs-link-hover-border-color: transparent transparent $secondary !default;
$nav-tabs-link-active-color: $black !default;
$nav-tabs-link-active-bg: transparent !default;
$nav-tabs-link-active-border-color: transparent transparent $primary !default;
// Navbar
$navbar-bg: $white !default;
$navbar-color: $black !default;
$navbar-border: $primary !default;
$navbar-active-bg: $white !default;
$navbar-active-color: $primary !default;
$navbar-active-border: $primary !default;
$navbar-hover-bg: $white !default;
$navbar-hover-color: $primary !default;
$navbar-hover-border: $primary !default;
$navbar-height: 50px !default;
$navbar-padding-y: 0 !default;
$navbar-padding-x: 0 !default;
$navbar-toggler-font-size: 24px !default;
// Dropdowns
$dropdown-link-color: $black !default;
$dropdown-link-hover-color: $black !default;
$dropdown-link-hover-bg: $light !default;
// Pagination
$pagination-color: $primary !default;
$pagination-disabled-bg: transparent !default;
$pagination-bg: transparent !default;
$pagination-active-bg: $primary !default;
$pagination-border-width: 0 !default;
// Forms
$input-font-family: $font-family-base !default;
$input-color: $black !default;
$input-disabled-bg: $light !default;
$input-border-color: $extra-light !default;
$input-border-width: 2px !default;
$input-focus-box-shadow: none !default;
$input-focus-border-color: $primary !default;
$input-placeholder-color: $secondary !default;
$input-group-addon-bg: $extra-light !default;
// Corteza specific variables
// Topbar
$topbar-height: 64px !default;
$topbar-bg: $body-bg !default;
// Sidebar
$sidebar-width: 320px !default;
$sidebar-bg: $white !default;
$corteza-specific: () !default;
$corteza-specific: map-merge(
(
"body-bg": $body-bg,
"topbar-height": $topbar-height,
"topbar-bg": $topbar-bg,
"sidebar-width": $sidebar-width,
"sidebar-bg": $sidebar-bg,
"font-regular": $font-regular,
"font-semibold": $font-semibold,
"font-family-base": $font-family-base,
"font-medium": $font-medium,
"btn-font-family": $btn-font-family,
"btn-padding-x": $btn-padding-x,
"btn-padding-y": $btn-padding-y,
),
$corteza-specific
);

View File

@@ -0,0 +1,144 @@
// Bootstrap functions
//
// Utility mixins and functions for evaluating source code across our variables, maps, and mixins.
// Ascending
// Used to evaluate Sass maps like our grid breakpoints.
@mixin _assert-ascending($map, $map-name) {
$prev-key: null;
$prev-num: null;
@each $key, $num in $map {
@if $prev-num == null or unit($num) == "%" or unit($prev-num) == "%" {
// Do nothing
} @else if not comparable($prev-num, $num) {
@warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
} @else if $prev-num >= $num {
@warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
}
$prev-key: $key;
$prev-num: $num;
}
}
// Starts at zero
// Used to ensure the min-width of the lowest breakpoint starts at 0.
@mixin _assert-starts-at-zero($map, $map-name: "$grid-breakpoints") {
@if length($map) > 0 {
$values: map-values($map);
$first-value: nth($values, 1);
@if $first-value != 0 {
@warn "First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}.";
}
}
}
// Replace `$search` with `$replace` in `$string`
// Used on our SVG icon backgrounds for custom forms.
//
// @author Hugo Giraudel
// @param {String} $string - Initial string
// @param {String} $search - Substring to replace
// @param {String} $replace ('') - New value
// @return {String} - Updated string
@function str-replace($string, $search, $replace: "") {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
// See https://codepen.io/kevinweber/pen/dXWoRw
//
// Requires the use of quotes around data URIs.
@function escape-svg($string) {
@if str-index($string, "data:image/svg+xml") {
@each $char, $encoded in $escaped-characters {
// Do not escape the url brackets
@if str-index($string, "url(") == 1 {
$string: url("#{str-replace(str-slice($string, 6, -3), $char, $encoded)}");
} @else {
$string: str-replace($string, $char, $encoded);
}
}
}
@return $string;
}
// Color contrast
@function color-yiq($color, $dark: $yiq-text-dark, $light: $yiq-text-light) {
$r: red($color);
$g: green($color);
$b: blue($color);
$yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
@if ($yiq >= $yiq-contrasted-threshold) {
@return $dark;
} @else {
@return $light;
}
}
// Retrieve color Sass maps
@function color($key: "blue") {
@return map-get($colors, $key);
}
@function theme-color($key: "primary") {
@return map-get($theme-colors, $key);
}
@function gray($key: "100") {
@return map-get($grays, $key);
}
// Request a theme color level
@function theme-color-level($color-name: "primary", $level: 0) {
$color: theme-color($color-name);
$color-base: if($level > 0, $black, $white);
$level: abs($level);
@return mix($color-base, $color, $level * $theme-color-interval);
}
// Return valid calc
@function add($value1, $value2, $return-calc: true) {
@if $value1 == null {
@return $value2;
}
@if $value2 == null {
@return $value1;
}
@if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) {
@return $value1 + $value2;
}
@return if($return-calc == true, calc(#{$value1} + #{$value2}), $value1 + unquote(" + ") + $value2);
}
@function subtract($value1, $value2, $return-calc: true) {
@if $value1 == null and $value2 == null {
@return null;
}
@if $value1 == null {
@return -$value2;
}
@if $value2 == null {
@return $value1;
}
@if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) {
@return $value1 - $value2;
}
@return if($return-calc == true, calc(#{$value1} - #{$value2}), $value1 + unquote(" - ") + $value2);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

17
server/auth/README.adoc Normal file
View File

@@ -0,0 +1,17 @@
= Corteza auth
.Main responsibilities:
- Sign-up protocol
- Local login via OAuth2 protocol
- Password set, reset protocol
- Email authentication protocol
- Session management
- Identity federation (login via external providers)
- Support OAuth2 flows
== Development
=== Templates
All (golang) templates can be found under `assets/templates`.
You can also find `scenarios.yaml` with list of all templates and scenaris with data that renders auth screens in all possible situations.

Some files were not shown because too many files have changed in this diff Show More