Merge branch 'main' into grcp-server-reflect

This commit is contained in:
Tim Möhlmann 2023-04-30 14:40:13 +03:00
commit f011882b2d
311 changed files with 15961 additions and 3609 deletions

View File

@ -1,7 +1,7 @@
module.exports = {
branches: [
{name: 'main'},
{name: '1.87.x', range: '1.87.x', channel: '1.87.x'}
{name: 'next'},
],
plugins: [
"@semantic-release/commit-analyzer"

View File

@ -16,11 +16,11 @@ ENV PROTOC_ARCH x86_64
## protoc and protoc-gen-grpc-web for later use
#######################
FROM ${BUILDARCH}-base
ARG PROTOC_VERSION=3.18.0
ARG PROTOC_VERSION=22.3
ARG PROTOC_ZIP=protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip
ARG GRPC_WEB_VERSION=1.3.0
ARG GATEWAY_VERSION=2.15.1
ARG VALIDATOR_VERSION=0.6.2
ARG GATEWAY_VERSION=2.15.2
ARG VALIDATOR_VERSION=0.10.1
# no arm specific version available and x86 works fine at the moment:
ARG GRPC_WEB=protoc-gen-grpc-web-${GRPC_WEB_VERSION}-linux-x86_64

View File

@ -73,7 +73,6 @@ COPY --from=go-stub /go/src/github.com/zitadel/zitadel/openapi/statik/statik.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/pkg/grpc pkg/grpc
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/openapi/v2/zitadel openapi/v2/zitadel
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/openapi/statik/statik.go openapi/statik/statik.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/protoc/protoc-gen-authoption/templates.gen.go internal/protoc/protoc-gen-authoption/templates.gen.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/protoc/protoc-gen-authoption/authoption/options.pb.go internal/protoc/protoc-gen-authoption/authoption/options.pb.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/docs/apis/proto docs/docs/apis/proto
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/docs/apis/assets docs/docs/apis/assets

View File

@ -15,17 +15,11 @@ protoc \
-I=/proto/include/ \
--go_out $GOPATH/src \
--go-grpc_out $GOPATH/src \
--validate_out=lang=go:${GOPATH}/src \
$(find ${PROTO_PATH} -iname *.proto)
# generate authoptions code from templates
go-bindata \
-pkg main \
-prefix internal/protoc/protoc-gen-authoption \
-o ${ZITADEL_PATH}/internal/protoc/protoc-gen-authoption/templates.gen.go \
${ZITADEL_PATH}/internal/protoc/protoc-gen-authoption/templates
# install authoption proto compiler
go install ${ZITADEL_PATH}/internal/protoc/protoc-gen-authoption
go install ${ZITADEL_PATH}/internal/protoc/protoc-gen-auth
# output folder for openapi v2
mkdir -p ${OPENAPI_PATH}
@ -39,28 +33,20 @@ protoc \
--grpc-gateway_opt logtostderr=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--authoption_out ${GRPC_PATH}/system \
--auth_out ${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/system.proto
# authoptions are generated into the wrong folder
mv ${ZITADEL_PATH}/pkg/grpc/system/zitadel/* ${ZITADEL_PATH}/pkg/grpc/system
rm -r ${ZITADEL_PATH}/pkg/grpc/system/zitadel
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--authoption_out ${GRPC_PATH}/admin \
--auth_out ${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/admin.proto
# authoptions are generated into the wrong folder
mv ${ZITADEL_PATH}/pkg/grpc/admin/zitadel/* ${ZITADEL_PATH}/pkg/grpc/admin
rm -r ${ZITADEL_PATH}/pkg/grpc/admin/zitadel
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
@ -69,14 +55,10 @@ protoc \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--authoption_out ${GRPC_PATH}/management \
--auth_out ${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/management.proto
# authoptions are generated into the wrong folder
mv ${ZITADEL_PATH}/pkg/grpc/management/zitadel/* ${ZITADEL_PATH}/pkg/grpc/management
rm -r ${ZITADEL_PATH}/pkg/grpc/management/zitadel
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
@ -85,14 +67,10 @@ protoc \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--authoption_out=${GRPC_PATH}/auth \
--auth_out=${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/auth.proto
# authoptions are generated into the wrong folder
mv ${ZITADEL_PATH}/pkg/grpc/auth/zitadel/* ${ZITADEL_PATH}/pkg/grpc/auth
rm -r ${ZITADEL_PATH}/pkg/grpc/auth/zitadel
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
@ -101,14 +79,10 @@ protoc \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--authoption_out=${GRPC_PATH}/user \
--auth_out=${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/user/v2alpha/user_service.proto
# authoptions are generated into the wrong folder
cp -r ${ZITADEL_PATH}/pkg/grpc/user/zitadel/* ${ZITADEL_PATH}/pkg/grpc
rm -r ${ZITADEL_PATH}/pkg/grpc/user/zitadel
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
@ -117,12 +91,8 @@ protoc \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--authoption_out=${GRPC_PATH}/session \
--auth_out=${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/session/v2alpha/session_service.proto
# authoptions are generated into the wrong folder
cp -r ${ZITADEL_PATH}/pkg/grpc/session/zitadel/* ${ZITADEL_PATH}/pkg/grpc
rm -r ${ZITADEL_PATH}/pkg/grpc/session/zitadel
echo "done generating grpc"

View File

@ -233,6 +233,8 @@ OIDC:
Path: /oidc/v1/end_session
Keys:
Path: /oauth/v2/keys
DeviceAuth:
Path: /oauth/v2/device_authorization
SAML:
ProviderConfig:
@ -319,6 +321,8 @@ SystemDefaults:
ApplicationKeySize: 2048
Multifactors:
OTP:
# If this is empty, the issuer is the requested domain
# This is helpful in scenarios with multiple ZITADEL environments or virtual instances
Issuer: "ZITADEL"
DomainVerification:
VerificationGenerator:
@ -394,6 +398,7 @@ Quotas:
Eventstore:
PushTimeout: 15s
AllowOrderByCreationDate: false
DefaultInstance:
InstanceName:

View File

@ -76,6 +76,7 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
nil,
nil,
nil,
nil,
)
if err != nil {

67
cmd/setup/10.go Normal file
View File

@ -0,0 +1,67 @@
package setup
import (
"context"
"database/sql"
_ "embed"
"time"
"github.com/cockroachdb/cockroach-go/v2/crdb"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
)
var (
//go:embed 10_create_temp_table.sql
correctCreationDate10CreateTable string
//go:embed 10_fill_table.sql
correctCreationDate10FillTable string
//go:embed 10_update.sql
correctCreationDate10Update string
)
type CorrectCreationDate struct {
dbClient *database.DB
FailAfter time.Duration
}
func (mig *CorrectCreationDate) Execute(ctx context.Context) (err error) {
ctx, cancel := context.WithTimeout(ctx, mig.FailAfter)
defer cancel()
for {
var affected int64
err = crdb.ExecuteTx(ctx, mig.dbClient.DB, nil, func(tx *sql.Tx) error {
if mig.dbClient.Type() == "cockroach" {
if _, err := tx.Exec("SET experimental_enable_temp_tables=on"); err != nil {
return err
}
}
_, err := tx.ExecContext(ctx, correctCreationDate10CreateTable)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, correctCreationDate10FillTable)
if err != nil {
return err
}
res, err := tx.ExecContext(ctx, correctCreationDate10Update)
if err != nil {
return err
}
affected, _ = res.RowsAffected()
logging.WithFields("count", affected).Info("creation dates changed")
return nil
})
if affected == 0 || err != nil {
return err
}
}
}
func (mig *CorrectCreationDate) String() string {
return "10_correct_creation_date"
}

View File

@ -0,0 +1,6 @@
CREATE temporary TABLE IF NOT EXISTS wrong_events (
instance_id TEXT
, event_sequence BIGINT
, current_cd TIMESTAMPTZ
, next_cd TIMESTAMPTZ
);

View File

@ -0,0 +1,19 @@
TRUNCATE wrong_events;
INSERT INTO wrong_events (
SELECT * FROM (
SELECT
instance_id
, event_sequence
, creation_date AS current_cd
, lead(creation_date) OVER (
PARTITION BY instance_id
ORDER BY event_sequence DESC
) AS next_cd
FROM
eventstore.events
) sub WHERE
current_cd < next_cd
ORDER BY
event_sequence DESC
);

1
cmd/setup/10_update.sql Normal file
View File

@ -0,0 +1 @@
UPDATE eventstore.events e SET creation_date = we.next_cd FROM wrong_events we WHERE e.event_sequence = we.event_sequence and e.instance_id = we.instance_id;

51
cmd/setup/cleanup.go Normal file
View File

@ -0,0 +1,51 @@
package setup
import (
"context"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/migration"
)
func NewCleanup() *cobra.Command {
return &cobra.Command{
Use: "cleanup",
Short: "cleans up migration if they got stuck",
Long: `cleans up migration if they got stuck`,
Run: func(cmd *cobra.Command, args []string) {
config := MustNewConfig(viper.GetViper())
Cleanup(config)
},
}
}
func Cleanup(config *Config) {
ctx := context.Background()
logging.Info("cleanup started")
dbClient, err := database.Connect(config.Database, false)
logging.OnError(err).Fatal("unable to connect to database")
es, err := eventstore.Start(&eventstore.Config{Client: dbClient})
logging.OnError(err).Fatal("unable to start eventstore")
migration.RegisterMappers(es)
step, err := migration.LatestStep(ctx, es)
logging.OnError(err).Fatal("unable to query latest migration")
if step.BaseEvent.EventType != migration.StartedType {
logging.Info("there is no stuck migration please run `zitadel setup`")
return
}
logging.WithFields("name", step.Name).Info("cleanup migration")
err = migration.CancelStep(ctx, es, step)
logging.OnError(err).Fatal("cleanup migration failed please retry")
}

View File

@ -65,6 +65,7 @@ type Steps struct {
s7LogstoreTables *LogstoreTables
s8AuthTokens *AuthTokenIndexes
s9EventstoreIndexes2 *EventstoreIndexesNew
CorrectCreationDate *CorrectCreationDate
}
type encryptionKeyConfig struct {

View File

@ -33,7 +33,8 @@ func (mig *externalConfigChange) Check() bool {
}
func (mig *externalConfigChange) Execute(ctx context.Context) error {
cmd, err := command.StartCommands(mig.es,
cmd, err := command.StartCommands(
mig.es,
systemdefaults.SystemDefaults{},
nil,
nil,
@ -50,6 +51,7 @@ func (mig *externalConfigChange) Execute(ctx context.Context) error {
nil,
nil,
nil,
nil,
)
if err != nil {

View File

@ -45,6 +45,8 @@ Requirements:
},
}
cmd.AddCommand(NewCleanup())
Flags(cmd)
return cmd
@ -88,6 +90,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
steps.s7LogstoreTables = &LogstoreTables{dbClient: dbClient.DB, username: config.Database.Username(), dbType: config.Database.Type()}
steps.s8AuthTokens = &AuthTokenIndexes{dbClient: dbClient}
steps.s9EventstoreIndexes2 = New09(dbClient)
steps.CorrectCreationDate.dbClient = dbClient
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil)
logging.OnError(err).Fatal("unable to start projections")
@ -123,6 +126,8 @@ func Setup(config *Config, steps *Steps, masterKey string) {
logging.OnError(err).Fatal("unable to migrate step 8")
err = migration.Migrate(ctx, eventstoreClient, steps.s9EventstoreIndexes2)
logging.OnError(err).Fatal("unable to migrate step 9")
err = migration.Migrate(ctx, eventstoreClient, steps.CorrectCreationDate)
logging.OnError(err).Fatal("unable to migrate step 10")
for _, repeatableStep := range repeatableSteps {
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)

View File

@ -30,3 +30,5 @@ FirstInstance:
MachineKey:
ExpirationDate:
Type:
CorrectCreationDate:
FailAfter: 5m

View File

@ -12,14 +12,13 @@ import (
"syscall"
"time"
"github.com/zitadel/saml/pkg/provider"
clockpkg "github.com/benbjohnson/clock"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v2/pkg/op"
"github.com/zitadel/saml/pkg/provider"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
@ -116,7 +115,7 @@ func startZitadel(config *Config, masterKey string) error {
return fmt.Errorf("cannot start queries: %w", err)
}
authZRepo, err := authz.Start(queries, dbClient, keys.OIDC, config.ExternalSecure)
authZRepo, err := authz.Start(queries, dbClient, keys.OIDC, config.ExternalSecure, config.Eventstore.AllowOrderByCreationDate)
if err != nil {
return fmt.Errorf("error starting authz repo: %w", err)
}
@ -147,6 +146,7 @@ func startZitadel(config *Config, masterKey string) error {
keys.OIDC,
keys.SAML,
&http.Client{},
authZRepo,
)
if err != nil {
return fmt.Errorf("cannot start commands: %w", err)
@ -229,11 +229,11 @@ func startAPIs(
if err != nil {
return fmt.Errorf("error creating api %w", err)
}
authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User)
authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User, config.Eventstore.AllowOrderByCreationDate)
if err != nil {
return fmt.Errorf("error starting auth repo: %w", err)
}
adminRepo, err := admin_es.Start(ctx, config.Admin, store, dbClient, eventstore)
adminRepo, err := admin_es.Start(ctx, config.Admin, store, dbClient, eventstore, config.Eventstore.AllowOrderByCreationDate)
if err != nil {
return fmt.Errorf("error starting admin repo: %w", err)
}
@ -249,7 +249,7 @@ func startAPIs(
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil {
return err
}
if err := apis.RegisterService(ctx, user.CreateServer(commands, queries)); err != nil {
if err := apis.RegisterService(ctx, user.CreateServer(commands, queries, keys.User)); err != nil {
return err
}
if err := apis.RegisterService(ctx, session.CreateServer(commands, queries)); err != nil {
@ -294,6 +294,7 @@ func startAPIs(
return fmt.Errorf("unable to start login: %w", err)
}
apis.RegisterHandlerOnPrefix(login.HandlerPrefix, l.Handler())
apis.HandleFunc(login.EndpointDeviceAuth, login.RedirectDeviceAuthToPrefix)
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
apis.RouteGRPC()

View File

@ -1,12 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -22,6 +22,7 @@
"main": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
"styles": ["src/styles.scss"],
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"],
@ -33,59 +34,24 @@
"@angular/common/locales/de",
"codemirror/mode/javascript/javascript",
"codemirror/mode/xml/xml",
"src/app/proto/generated/zitadel/admin_pb",
"src/app/proto/generated/zitadel/org_pb",
"src/app/proto/generated/zitadel/management_pb",
"src/app/proto/generated/zitadel/user_pb",
"src/app/proto/generated/**",
"google-protobuf/google/protobuf/empty_pb",
"file-saver",
"qrcode"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": {
"scripts": true,
"fonts": {
"inline": true
},
"styles": {
"minify": true,
"inlineCritical": false
}
},
"namedChunks": true
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": {
"scripts": true,
"fonts": {
"inline": false
},
"styles": {
"minify": true,
"inlineCritical": false
}
},
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "7mb",
"maximumWarning": "6mb",
"maximumError": "7mb"
},
{
@ -94,16 +60,21 @@
"maximumError": "10kb"
}
],
"serviceWorker": false,
"ngswConfigPath": "ngsw-config.json"
"outputHashing": "all"
},
"development": {}
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
"defaultConfiguration": "development"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {},
"configurations": {
"production": {
"browserTarget": "console:build:production"
@ -123,13 +94,12 @@
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": ["zone.js"],
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
"styles": ["./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css", "src/styles.scss"],
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"]
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
},
"lint": {
@ -137,35 +107,12 @@
"options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js"
},
"configurations": {
"production": {
"devServerTarget": "console:serve:production"
},
"development": {
"devServerTarget": "console:serve:development"
}
},
"defaultConfiguration": "development"
}
}
}
},
"cli": {
"analytics": "2b4e8e6c-f053-4562-b7a6-00c6c06a6791",
"analytics": false,
"schematicCollections": ["@angular-eslint/schematics"]
},
"schematics": {
"@angular-eslint/schematics:application": {
"setParserOptionsProject": true
},
"@angular-eslint/schematics:library": {
"setParserOptionsProject": true
}
}
}

4793
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,18 +12,18 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^15.2.2",
"@angular/cdk": "^15.2.2",
"@angular/common": "^15.2.2",
"@angular/compiler": "^15.2.2",
"@angular/core": "^15.2.2",
"@angular/forms": "^15.2.2",
"@angular/material": "^15.2.2",
"@angular/material-moment-adapter": "^15.2.2",
"@angular/platform-browser": "^15.2.2",
"@angular/platform-browser-dynamic": "^15.2.2",
"@angular/router": "^15.2.2",
"@angular/service-worker": "^15.2.2",
"@angular/animations": "^15.2.6",
"@angular/cdk": "^15.2.6",
"@angular/common": "^15.2.6",
"@angular/compiler": "^15.2.6",
"@angular/core": "^15.2.6",
"@angular/forms": "^15.2.6",
"@angular/material": "^15.2.6",
"@angular/material-moment-adapter": "^15.2.6",
"@angular/platform-browser": "^15.2.6",
"@angular/platform-browser-dynamic": "^15.2.6",
"@angular/router": "^15.2.6",
"@angular/service-worker": "^15.2.6",
"@ctrl/ngx-codemirror": "^6.1.0",
"@grpc/grpc-js": "^1.8.12",
"@ngx-translate/core": "^14.0.0",
@ -41,7 +41,7 @@
"google-protobuf": "^3.21.2",
"grpc-web": "^1.4.1",
"i18n-iso-countries": "^7.5.0",
"libphonenumber-js": "^1.10.19",
"libphonenumber-js": "^1.10.24",
"material-design-icons-iconfont": "^6.1.1",
"moment": "^2.29.4",
"ngx-color": "^8.0.3",
@ -49,36 +49,36 @@
"tinycolor2": "^1.6.0",
"tslib": "^2.4.1",
"uuid": "^9.0.0",
"zone.js": "~0.12.0"
"zone.js": "~0.13.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.2.2",
"@angular-eslint/builder": "^15.2.1",
"@angular-eslint/eslint-plugin": "^15.2.1",
"@angular-eslint/eslint-plugin-template": "^15.2.1",
"@angular-eslint/schematics": "^15.2.1",
"@angular-eslint/template-parser": "^15.2.1",
"@angular/cli": "^15.2.2",
"@angular/compiler-cli": "^15.2.2",
"@angular/language-service": "^15.2.2",
"@angular-devkit/build-angular": "^15.2.5",
"@angular-eslint/builder": "15.2.1",
"@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.5",
"@angular/compiler-cli": "^15.2.6",
"@angular/language-service": "^15.2.6",
"@bufbuild/buf": "^1.14.0",
"@types/jasmine": "~4.3.0",
"@types/jasminewd2": "~2.0.10",
"@types/jsonwebtoken": "^9.0.1",
"@types/node": "^18.13.0",
"@types/node": "^18.15.11",
"@types/qrcode": "^1.5.0",
"@typescript-eslint/eslint-plugin": "5.54.1",
"@typescript-eslint/parser": "5.54.1",
"@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2",
"codelyzer": "^6.0.2",
"eslint": "^8.33.0",
"jasmine-core": "~4.5.0",
"jasmine-core": "~4.6.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.1",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^2.0.0",
"prettier": "^2.8.4",
"prettier": "^2.8.7",
"prettier-plugin-organize-imports": "^3.2.2",
"protractor": "~7.0.0",
"typescript": "^4.9.5"

View File

@ -201,7 +201,7 @@ export class AppComponent implements OnDestroy {
}
});
this.activatedRoute.queryParams.pipe(filter((params) => !!params.org)).subscribe((params) => {
this.activatedRoute.queryParams.pipe(filter((params) => !!params['org'])).subscribe((params) => {
const { org } = params;
this.authService.getActiveOrg(org);
});
@ -252,7 +252,7 @@ export class AppComponent implements OnDestroy {
}
public prepareRoute(outlet: RouterOutlet): boolean {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
}
public onSetTheme(theme: string): void {
@ -267,15 +267,15 @@ export class AppComponent implements OnDestroy {
}
private setLanguage(): void {
this.translate.addLangs(['de', 'en', 'fr', 'it', 'ja', 'pl', 'zh']);
this.translate.addLangs(['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh']);
this.translate.setDefaultLang('en');
this.authService.user.subscribe((userprofile) => {
if (userprofile) {
const cropped = navigator.language.split('-')[0] ?? 'en';
const fallbackLang = cropped.match(/de|en|fr|it|ja|pl|zh/) ? cropped : 'en';
const fallbackLang = cropped.match(/de|en|es|fr|it|ja|pl|zh/) ? cropped : 'en';
const lang = userprofile?.human?.profile?.preferredLanguage.match(/de|en|fr|it|ja|pl|zh/)
const lang = userprofile?.human?.profile?.preferredLanguage.match(/de|en|es|fr|it|ja|pl|zh/)
? userprofile.human.profile?.preferredLanguage
: fallbackLang;
this.translate.use(lang);

View File

@ -2,6 +2,7 @@ import { CommonModule, registerLocaleData } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import localeDe from '@angular/common/locales/de';
import localeEn from '@angular/common/locales/en';
import localeEs from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
@ -64,6 +65,8 @@ registerLocaleData(localeDe);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/de.json'));
registerLocaleData(localeEn);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/en.json'));
registerLocaleData(localeEs);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/es.json'));
registerLocaleData(localeFr);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/fr.json'));
registerLocaleData(localeIt);

View File

@ -16,7 +16,7 @@ export class UserGuard implements CanActivate {
state: RouterStateSnapshot,
): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.user.pipe(
map((user) => user?.id !== route.params.id),
map((user) => user?.id !== route.params['id']),
tap((isNotMe) => {
if (!isNotMe) {
this.router.navigate(['/users', 'me']);

View File

@ -16,9 +16,9 @@
<input cnslInput [matDatepicker]="picker" [min]="startDate" [formControl]="dateControl" />
<mat-datepicker-toggle style="top: 0" cnslSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker [startAt]="startDate"></mat-datepicker>
<span cnslError *ngIf="dateControl?.errors?.matDatepickerMin?.min">
<span cnslError *ngIf="dateControl && dateControl.errors && dateControl.errors['matDatepickerMin']?.min">
{{ 'USER.MACHINE.CHOOSEDATEAFTER' | translate }}:
{{ dateControl.errors?.matDatepickerMin.min.toDate() | localizedDate : 'EEE dd. MMM' }}
{{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate : 'EEE dd. MMM' }}
</span>
</cnsl-form-field>
</div>

View File

@ -7,9 +7,9 @@
<input cnslInput [matDatepicker]="picker" [min]="startDate" [formControl]="dateControl" />
<mat-datepicker-toggle style="top: 0" cnslSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker startView="year" [startAt]="startDate"></mat-datepicker>
<span cnslError *ngIf="dateControl?.errors?.matDatepickerMin?.min">
<span cnslError *ngIf="dateControl && dateControl.errors && dateControl.errors['matDatepickerMin']?.min">
{{ 'USER.PERSONALACCESSTOKEN.ADD.CHOOSEDATEAFTER' | translate }}:
{{ dateControl.errors?.matDatepickerMin.min.toDate() | localizedDate : 'EEE dd. MMM' }}
{{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate : 'EEE dd. MMM' }}
</span>
</cnsl-form-field>
</div>

View File

@ -16,8 +16,6 @@
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayHasBackdrop]="true"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayPositions]="positions"
[cdkConnectedOverlayOrigin]="triggereventfilter"

View File

@ -24,7 +24,7 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
public states: OrgState[] = [OrgState.ORG_STATE_ACTIVE, OrgState.ORG_STATE_INACTIVE, OrgState.ORG_STATE_REMOVED];
constructor(router: Router, protected route: ActivatedRoute) {
constructor(router: Router, protected override route: ActivatedRoute) {
super(router, route);
}
@ -137,7 +137,7 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
public override emitFilter(): void {
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);

View File

@ -110,7 +110,7 @@ export class FilterProjectComponent extends FilterComponent implements OnInit {
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
public override emitFilter(): void {
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);

View File

@ -226,7 +226,7 @@ export class FilterUserGrantsComponent extends FilterComponent implements OnInit
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
public override emitFilter(): void {
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);

View File

@ -236,7 +236,7 @@ export class FilterUserComponent extends FilterComponent implements OnInit {
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
public override emitFilter(): void {
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);

View File

@ -15,8 +15,6 @@
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayHasBackdrop]="true"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayPositions]="positions"
[cdkConnectedOverlayOrigin]="trigger"

View File

@ -1,5 +1,14 @@
<ng-template #labelTemplate>
<label class="cnsl-label-wrapper" [attr.for]="_control.id" [attr.aria-owns]="_control.id">
<ng-content select="cnsl-label"></ng-content>
<span *ngIf="_control.required && !hideRequiredMarker" aria-hidden="true" class="cnsl-form-field-required-marker"
>*</span
>
</label>
</ng-template>
<div class="cnsl-form-field-wrapper" (click)="_control.onContainerClick && _control.onContainerClick($event)">
<ng-content select="cnsl-label"></ng-content>
<ng-template [ngTemplateOutlet]="labelTemplate"></ng-template>
<div class="cnsl-rel" #inputContainer>
<ng-content></ng-content>
<ng-content select="cnslSuffix"></ng-content>

View File

@ -51,6 +51,7 @@ interface ValidationError {
'[class.ng-valid]': '_shouldForward("valid")',
'[class.ng-invalid]': '_shouldForward("invalid")',
'[class.ng-pending]': '_shouldForward("pending")',
'[class.ng-required]': '_control.required',
'[class.cnsl-form-field-disabled]': '_control.disabled',
'[class.cnsl-form-field-autofilled]': '_control.autofilled',
'[class.cnsl-focused]': '_control.focused',
@ -69,6 +70,7 @@ export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestr
@ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>;
@ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>;
@Input() public disableValidationErrors = false;
@Input() public hideRequiredMarker = false;
get _control(): MatFormFieldControl<any> {
return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic;
@ -95,7 +97,7 @@ export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestr
}
constructor(
public _elementRef: ElementRef,
public override _elementRef: ElementRef,
private _changeDetectorRef: ChangeDetectorRef,
@Inject(ElementRef)
_labelOptions: // Use `ElementRef` here so Angular has something to inject.

View File

@ -24,6 +24,12 @@ export function requiredValidator(c: AbstractControl): ValidationErrors | null {
return i18nErr(Validators.required(c), 'ERRORS.REQUIRED');
}
export function minArrayLengthValidator(minArrLength: number): ValidatorFn {
return (c: AbstractControl): ValidationErrors | null => {
return arrayLengthValidator(c, minArrLength, 'ERRORS.ATLEASTONE');
};
}
export function emailValidator(c: AbstractControl): ValidationErrors | null {
return i18nErr(Validators.email(c), 'ERRORS.NOTANEMAIL');
}
@ -56,6 +62,12 @@ function regexpValidator(c: AbstractControl, regexp: RegExp, i18nKey: string): V
return !c.value || regexp.test(c.value) ? null : i18nErr({ invalid: true }, i18nKey, { regexp: regexp });
}
function arrayLengthValidator(c: AbstractControl, length: number, i18nKey: string): ValidationErrors | null {
const arr: string[] = c.value;
const invalidStrings: string[] = arr.filter((val: string) => val.trim() === '');
return arr && invalidStrings.length === 0 && arr.length >= length ? null : i18nErr({ invalid: true }, i18nKey);
}
function i18nErr(err: ValidationErrors | null | undefined, i18nKey: string, params?: any): ValidationErrors | null {
if (err === null) {
return null;

View File

@ -110,8 +110,6 @@
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayPositions]="positions"
@ -221,8 +219,6 @@
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="accounttrigger"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayPositions]="accountCardPositions"

View File

@ -199,7 +199,7 @@ export class InputDirective
protected _type: string = 'text';
/** An object used to control when error messages are shown. */
@Input() errorStateMatcher!: ErrorStateMatcher;
@Input() override errorStateMatcher!: ErrorStateMatcher;
/**
* Implemented as part of MatFormFieldControl.
@ -241,7 +241,7 @@ export class InputDirective
protected _elementRef: ElementRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
protected _platform: Platform,
/** @docs-private */
@Optional() @Self() public ngControl: NgControl,
@Optional() @Self() public override ngControl: NgControl,
@Optional() _parentForm: NgForm,
@Optional() _parentFormGroup: FormGroupDirective,
_defaultErrorStateMatcher: ErrorStateMatcher,

View File

@ -9,23 +9,31 @@
$foreground: map-get($theme, foreground);
$secondary-text: map-get($foreground, secondary-text);
.cnsl-label {
display: block;
.cnsl-label-wrapper {
display: flex;
font-size: 12px;
color: $secondary-text;
transition: color 0.2s ease;
margin-bottom: 4px;
font-weight: 400;
.cnsl-label {
display: block;
}
.cnsl-form-field-required-marker {
margin-left: 1px;
}
}
.cnsl-form-field-disabled {
.cnsl-label {
.cnsl-label-wrapper {
color: if($is-dark-theme, #ffffff80, #00000061);
}
}
.cnsl-form-field-invalid {
.cnsl-label {
.cnsl-label-wrapper {
color: $warn-color;
}
}

View File

@ -27,8 +27,8 @@ export class MetadataComponent implements OnChanges {
constructor() {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.metadata?.currentValue) {
this.dataSource = new MatTableDataSource<Metadata.AsObject>(changes.metadata.currentValue);
if (changes['metadata']?.currentValue) {
this.dataSource = new MatTableDataSource<Metadata.AsObject>(changes['metadata'].currentValue);
}
}
}

View File

@ -111,7 +111,7 @@
<span class="label">{{ 'MENU.DASHBOARD' | translate }}</span>
</a>
<ng-container class="org-list" *ngIf="org" [@navAnimation]="org">
<ng-container class="org-list" *ngIf="org">
<ng-template cnslHasRole [hasRole]="['org.read']">
<a
class="nav-item"
@ -232,8 +232,6 @@
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayPositions]="positions"

View File

@ -198,6 +198,12 @@
.state-circle {
display: none;
}
.action-card {
.action-content {
opacity: 1;
}
}
}
}
}

View File

@ -4,7 +4,10 @@
<ng-template #showSpinner>
<div
*ngIf="password?.errors?.minlength || password?.value?.length === 0 as currentError; else trueminlength"
*ngIf="
(password && password.errors && password.errors['minlength']) || password?.value?.length === 0 as currentError;
else trueminlength
"
class="complexity-sp-wrapper"
>
<mat-progress-spinner
@ -28,23 +31,23 @@
</span>
</div>
<div class="val" *ngIf="this.policy.hasSymbol">
<i *ngIf="password?.pristine || password?.errors?.errorssymbolerror" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.errorssymbolerror" class="las la-check green"></i>
<i *ngIf="password?.pristine || password?.errors?.['errorssymbolerror']" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.['errorssymbolerror']" class="las la-check green"></i>
<span class="cnsl-secondary-text"> {{ 'ERRORS.SYMBOLERROR' | translate }}</span>
</div>
<div class="val" *ngIf="this.policy.hasNumber">
<i *ngIf="password?.pristine || password?.errors?.errorsnumbererror" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.errorsnumbererror" class="las la-check green"></i>
<i *ngIf="password?.pristine || password?.errors?.['errorsnumbererror']" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.['errorsnumbererror']" class="las la-check green"></i>
<span class="cnsl-secondary-text"> {{ 'ERRORS.NUMBERERROR' | translate }}</span>
</div>
<div class="val" *ngIf="this.policy.hasUppercase">
<i *ngIf="password?.pristine || password?.errors?.errorsuppercasemissing" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.errorsuppercasemissing" class="las la-check green"></i>
<i *ngIf="password?.pristine || password?.errors?.['errorsuppercasemissing']" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.['errorsuppercasemissing']" class="las la-check green"></i>
<span class="cnsl-secondary-text"> {{ 'ERRORS.UPPERCASEMISSING' | translate }}</span>
</div>
<div class="val" *ngIf="this.policy.hasLowercase">
<i *ngIf="password?.pristine || password?.errors?.errorslowercasemissing" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.errorslowercasemissing" class="las la-check green"></i>
<i *ngIf="password?.pristine || password?.errors?.['errorslowercasemissing']" class="las la-times red"></i>
<i *ngIf="password?.dirty && !password?.errors?.['errorslowercasemissing']" class="las la-check green"></i>
<span class="cnsl-secondary-text">{{ 'ERRORS.LOWERCASEMISSING' | translate }}</span>
</div>
</div>

View File

@ -109,7 +109,7 @@ export class LoginTextsComponent implements OnInit, OnDestroy {
@Input() public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public KeyNamesArray: string[] = KeyNamesArray;
public LOCALES: string[] = ['de', 'en', 'fr', 'it', 'ja', 'pl', 'zh'];
public LOCALES: string[] = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh'];
private sub: Subscription = new Subscription();

View File

@ -46,7 +46,7 @@
<div class="message-text-actions">
<button
class="reset-button"
*ngIf="(getCustomInitMessageTextMap$ | async) && (getCustomInitMessageTextMap$ | async)?.isDefault === false"
*ngIf="(getCustomInitMessageTextMap$ | async) && (getCustomInitMessageTextMap$ | async)?.['isDefault'] === false"
[disabled]="(canWrite$ | async) === false"
(click)="resetDefault()"
color="message-text-warn"

View File

@ -441,7 +441,7 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
};
public locale: string = 'en';
public LOCALES: string[] = ['de', 'en', 'fr', 'it', 'ja', 'pl', 'zh'];
public LOCALES: string[] = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh'];
private sub: Subscription = new Subscription();
public canWrite$: Observable<boolean> = this.authService.isAllowed([
this.serviceType === PolicyComponentServiceType.ADMIN

View File

@ -45,13 +45,13 @@ export class ProjectMembersComponent {
private route: ActivatedRoute,
) {
this.route.data.pipe(take(1)).subscribe((data) => {
this.projectType = data.type;
this.projectType = data['type'];
this.getRoleOptions();
this.route.params.subscribe((params) => {
this.projectId = params.projectid;
this.grantId = params.grantid;
this.projectId = params['projectid'];
this.grantId = params['grantid'];
this.loadMembers();
});
});

View File

@ -1,54 +1,65 @@
<form [formGroup]="form" class="attribute-form">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.LDAPIDATTRIBUTE' | translate }}*</cnsl-label>
<input cnslInput formControlName="idAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.AVATARURLATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="avatarUrlAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.DISPLAYNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="displayNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILATTRIBUTEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.FIRSTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="firstNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.LASTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="lastNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NICKNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="nickNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDLANGUAGEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredLanguageAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDUSERNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredUsernameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PROFILEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="profileAttribute" />
<cnsl-label>{{ 'IDP.LDAPIDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="idAttribute" required />
</cnsl-form-field>
<div class="attribute-more-row">
<span>{{ 'ACTIONS.MORE' | translate }}</span>
<button (click)="showMore = !showMore" type="button" mat-icon-button>
<mat-icon *ngIf="showMore">keyboard_arrow_up</mat-icon>
<mat-icon *ngIf="!showMore">keyboard_arrow_down</mat-icon>
</button>
</div>
<ng-container *ngIf="showMore">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.AVATARURLATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="avatarUrlAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.DISPLAYNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="displayNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILATTRIBUTEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.FIRSTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="firstNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.LASTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="lastNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NICKNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="nickNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDLANGUAGEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredLanguageAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDUSERNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredUsernameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PROFILEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="profileAttribute" />
</cnsl-form-field>
</ng-container>
</form>

View File

@ -4,3 +4,8 @@
max-width: 400px;
padding-bottom: 1rem;
}
.attribute-more-row {
display: flex;
align-items: center;
}

View File

@ -29,6 +29,7 @@ export class LDAPAttributesComponent implements OnChanges, OnDestroy {
profileAttribute: new FormControl('', []),
});
public showMore: boolean = false;
constructor() {
this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
if (value) {

View File

@ -25,9 +25,14 @@
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateClientSecret" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATECLIENTSECRET' | translate
}}</mat-checkbox>
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateClientSecret"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATECLIENTSECRET' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateClientSecret)" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />
@ -85,7 +90,7 @@
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-form-field class="formfield" *ngIf="tenantType?.value === AzureTenantIDType">
<cnsl-label>{{ 'IDP.AZUREADTENANTID' | translate }}</cnsl-label>
<input cnslInput formControlName="tenantId" />
</cnsl-form-field>

View File

@ -44,7 +44,10 @@ export class ProviderAzureADComponent {
public provider?: Provider.AsObject;
public updateClientSecret: boolean = false;
public AzureTenantIDType: number = 3;
public tenantTypes = [
this.AzureTenantIDType,
AzureADTenantType.AZURE_AD_TENANT_TYPE_COMMON,
AzureADTenantType.AZURE_AD_TENANT_TYPE_ORGANISATIONS,
AzureADTenantType.AZURE_AD_TENANT_TYPE_CONSUMERS,
@ -86,7 +89,7 @@ export class ProviderAzureADComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
@ -126,15 +129,34 @@ export class ProviderAzureADComponent {
: new MgmtGetProviderByIDRequest();
req.setId(id);
this.service
.getProviderByID(req)
.getProviderID(req)
.then((resp) => {
this.provider = resp.idp;
const object = resp.toObject();
this.provider = object.idp;
this.loading = false;
if (this.provider?.config?.azureAd) {
this.form.patchValue(this.provider.config.azureAd);
this.name?.setValue(this.provider.name);
this.tenantId?.setValue(this.provider.config.azureAd.tenant?.tenantId);
this.tenantType?.setValue(this.provider.config.azureAd.tenant?.tenantType);
const tenant = resp.getIdp()?.getConfig()?.getAzureAd()?.getTenant();
if (tenant) {
switch (tenant.getTypeCase()) {
case AzureADTenant.TypeCase.TENANT_ID:
this.tenantId?.setValue(tenant.getTenantId());
this.tenantType?.setValue(this.AzureTenantIDType);
break;
case AzureADTenant.TypeCase.TENANT_TYPE:
this.tenantType?.setValue(tenant.getTenantType());
this.tenantId?.setValue('');
break;
case AzureADTenant.TypeCase.TYPE_NOT_SET:
this.tenantType?.setValue(this.AzureTenantIDType);
break;
}
}
}
})
.catch((error) => {
@ -159,8 +181,11 @@ export class ProviderAzureADComponent {
req.setEmailVerified(this.emailVerified?.value);
const tenant = new AzureADTenant();
tenant.setTenantId(this.tenantId?.value);
tenant.setTenantType(this.tenantType?.value);
if (this.tenantType?.value === this.AzureTenantIDType) {
tenant.setTenantId(this.tenantId?.value);
} else {
tenant.setTenantType(this.tenantType?.value);
}
req.setTenant(tenant);
req.setScopesList(this.scopesList?.value);
@ -194,9 +219,11 @@ export class ProviderAzureADComponent {
req.setEmailVerified(this.emailVerified?.value);
const tenant = new AzureADTenant();
tenant.setTenantId(this.tenantId?.value);
tenant.setTenantType(this.tenantType?.value);
if (this.tenantType?.value === this.AzureTenantIDType) {
tenant.setTenantId(this.tenantId?.value);
} else {
tenant.setTenantType(this.tenantType?.value);
}
req.setTenant(tenant);
req.setScopesList(this.scopesList?.value);

View File

@ -40,9 +40,14 @@
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateClientSecret" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATECLIENTSECRET' | translate
}}</mat-checkbox>
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateClientSecret"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATECLIENTSECRET' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateClientSecret)" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />

View File

@ -80,7 +80,7 @@ export class ProviderGithubESComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
@ -153,6 +153,7 @@ export class ProviderGithubESComponent {
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setScopesList(this.scopesList?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service
@ -183,6 +184,7 @@ export class ProviderGithubESComponent {
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setScopesList(this.scopesList?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service

View File

@ -21,9 +21,14 @@
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateClientSecret" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATECLIENTSECRET' | translate
}}</mat-checkbox>
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateClientSecret"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATECLIENTSECRET' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateClientSecret)" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />

View File

@ -78,7 +78,7 @@ export class ProviderGithubComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:

View File

@ -30,9 +30,14 @@
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateClientSecret" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATECLIENTSECRET' | translate
}}</mat-checkbox>
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateClientSecret"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATECLIENTSECRET' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateClientSecret)" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />

View File

@ -79,7 +79,7 @@ export class ProviderGitlabSelfHostedComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:

View File

@ -20,9 +20,14 @@
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateClientSecret" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATECLIENTSECRET' | translate
}}</mat-checkbox>
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateClientSecret"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATECLIENTSECRET' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateClientSecret)" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />

View File

@ -78,7 +78,7 @@ export class ProviderGitlabComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:

View File

@ -20,9 +20,13 @@
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateClientSecret" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATECLIENTSECRET' | translate
}}</mat-checkbox>
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateClientSecret"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATECLIENTSECRET' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateClientSecret)" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />

View File

@ -78,7 +78,7 @@ export class ProviderGoogleComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:

View File

@ -51,7 +51,7 @@ export class ProviderJWTComponent {
breadcrumbService: BreadcrumbService,
) {
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:

View File

@ -17,12 +17,13 @@
<div class="identity-provider-content">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
<input cnslInput formControlName="name" required />
</cnsl-form-field>
<h2 class="subheader">{{ 'IDP.LDAPCONNECTION' | translate }}</h2>
<cnsl-string-list
class="string-list-component-wrapper"
title="{{ 'IDP.SERVERS' | translate }}"
formControlName="serversList"
[required]="true"
@ -30,21 +31,31 @@
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.BASEDN' | translate }}</cnsl-label>
<input cnslInput formControlName="baseDn" />
<input cnslInput formControlName="baseDn" required />
</cnsl-form-field>
<div [ngClass]="{ 'identity-provider-2-col': !provider }">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.BINDDN' | translate }}</cnsl-label>
<input cnslInput formControlName="bindDn" />
<input cnslInput formControlName="bindDn" required />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateBindPassword" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATEBINDPASSWORD' | translate
}}</mat-checkbox>
<cnsl-form-field *ngIf="!provider || (provider && updateBindPassword)" class="formfield">
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateBindPassword"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATEBINDPASSWORD' | translate }}</mat-checkbox
>
<cnsl-form-field class="formfield pwd" [ngClass]="{ show: !provider || (provider && updateBindPassword) }">
<cnsl-label>{{ 'IDP.BINDPASSWORD' | translate }}</cnsl-label>
<input cnslInput formControlName="bindPassword" />
<input
cnslInput
formControlName="bindPassword"
type="password"
autocomplete="new-password"
[required]="!provider"
/>
</cnsl-form-field>
</div>
@ -52,16 +63,18 @@
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.USERBASE' | translate }}</cnsl-label>
<input cnslInput formControlName="userBase" />
<input cnslInput formControlName="userBase" required />
</cnsl-form-field>
<cnsl-string-list
class="string-list-component-wrapper"
title="{{ 'IDP.USERFILTERS' | translate }}"
formControlName="userFiltersList"
[required]="true"
></cnsl-string-list>
<cnsl-string-list
class="string-list-component-wrapper"
title="{{ 'IDP.USEROBJECTCLASSES' | translate }}"
formControlName="userObjectClassesList"
[required]="true"
@ -69,22 +82,11 @@
<div class="identity-provider-optional-h-wrapper">
<h2>{{ 'IDP.LDAPATTRIBUTES' | translate }}</h2>
<button (click)="showAttributes = !showAttributes" type="button" mat-icon-button>
<mat-icon *ngIf="showAttributes">keyboard_arrow_up</mat-icon>
<mat-icon *ngIf="!showAttributes">keyboard_arrow_down</mat-icon>
</button>
<span *ngIf="!provider?.config?.ldap?.attributes?.idAttribute" class="state error">{{
'IDP.REQUIRED' | translate
}}</span>
</div>
<div *ngIf="showAttributes">
<cnsl-ldap-attributes
[initialAttributes]="provider?.config?.ldap?.attributes"
(attributesChanged)="attributes = $event"
></cnsl-ldap-attributes>
</div>
<cnsl-ldap-attributes
[initialAttributes]="provider?.config?.ldap?.attributes"
(attributesChanged)="attributes = $event"
></cnsl-ldap-attributes>
<div class="identity-provider-optional-h-wrapper">
<h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
@ -113,7 +115,7 @@
color="primary"
mat-raised-button
class="continue-button"
[disabled]="(form.invalid && attributes.toObject().idAttribute !== '') || form.disabled"
[disabled]="!form.valid || !attributes.toObject().idAttribute || form.disabled"
type="submit"
>
<span *ngIf="id">{{ 'ACTIONS.SAVE' | translate }}</span>

View File

@ -20,7 +20,7 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { requiredValidator } from '../../form-field/validators/validators';
import { minArrayLengthValidator, requiredValidator } from '../../form-field/validators/validators';
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
@ -30,7 +30,6 @@ import { PolicyComponentServiceType } from '../../policies/policy-component-type
})
export class ProviderLDAPComponent {
public updateBindPassword: boolean = false;
public showAttributes: boolean = false;
public showOptional: boolean = false;
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
public attributes: LDAPAttributes = new LDAPAttributes();
@ -54,15 +53,15 @@ export class ProviderLDAPComponent {
) {
this.form = new FormGroup({
name: new FormControl('', [requiredValidator]),
serversList: new FormControl('', [requiredValidator]),
serversList: new FormControl<string[]>([''], [minArrayLengthValidator(1)]),
baseDn: new FormControl('', [requiredValidator]),
bindDn: new FormControl('', [requiredValidator]),
bindPassword: new FormControl('', [requiredValidator]),
userBase: new FormControl('', [requiredValidator]),
userFiltersList: new FormControl('', [requiredValidator]),
userObjectClassesList: new FormControl('', [requiredValidator]),
userFiltersList: new FormControl<string[]>([''], [minArrayLengthValidator(1)]),
userObjectClassesList: new FormControl<string[]>([''], [minArrayLengthValidator(1)]),
timeout: new FormControl<number>(0),
startTls: new FormControl(false),
startTls: new FormControl<boolean>(false),
});
this.authService
@ -83,7 +82,7 @@ export class ProviderLDAPComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
@ -112,6 +111,7 @@ export class ProviderLDAPComponent {
if (this.id) {
this.getData(this.id);
this.bindPassword?.setValidators([]);
this.bindPassword?.updateValueAndValidity();
}
});
}
@ -125,12 +125,25 @@ export class ProviderLDAPComponent {
this.service
.getProviderByID(req)
.then((resp) => {
this.provider = resp.idp;
this.loading = false;
if (this.provider?.config?.ldap) {
this.form.patchValue(this.provider.config.ldap);
if (resp.idp) {
this.provider = resp.idp;
this.loading = false;
this.name?.setValue(this.provider.name);
this.timeout?.setValue(this.provider.config.ldap.timeout?.seconds);
const config = this.provider?.config?.ldap;
if (config) {
this.serversList?.setValue(config.serversList);
this.startTls?.setValue(config.startTls);
this.baseDn?.setValue(config.baseDn);
this.bindDn?.setValue(config.bindDn);
this.userBase?.setValue(config.userBase);
this.userObjectClassesList?.setValue(config.userObjectClassesList);
this.userFiltersList?.setValue(config.userFiltersList);
if (this.provider?.config?.ldap?.timeout?.seconds) {
this.timeout?.setValue(this.provider?.config?.ldap?.timeout?.seconds);
}
}
}
})
.catch((error) => {

View File

@ -13,40 +13,45 @@
<p class="identity-provider-desc cnsl-secondary-text">{{ 'IDP.CREATE.OAUTH.DESCRIPTION' | translate }}</p>
<form [formGroup]="form" (ngSubmit)="submitForm()">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.AUTHORIZATIONENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="authorizationEndpoint" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.TOKENENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="tokenEndpoint" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.USERENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="userEndpoint" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.IDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="idAttribute" />
</cnsl-form-field>
<div class="identity-provider-content">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.AUTHORIZATIONENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="authorizationEndpoint" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.TOKENENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="tokenEndpoint" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.USERENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="userEndpoint" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.IDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="idAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox *ngIf="provider" [(ngModel)]="updateClientSecret" [ngModelOptions]="{ standalone: true }">{{
'IDP.UPDATECLIENTSECRET' | translate
}}</mat-checkbox>
<mat-checkbox
class="update-secret-checkbox"
*ngIf="provider"
[(ngModel)]="updateClientSecret"
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATECLIENTSECRET' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateClientSecret)" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />

View File

@ -81,7 +81,7 @@ export class ProviderOAuthComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
@ -155,6 +155,7 @@ export class ProviderOAuthComponent {
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setScopesList(this.scopesList?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service
@ -186,6 +187,7 @@ export class ProviderOAuthComponent {
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setScopesList(this.scopesList?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service

View File

@ -59,7 +59,7 @@ export class ProviderOIDCComponent {
});
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data.serviceType;
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
@ -130,6 +130,7 @@ export class ProviderOIDCComponent {
req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value);
req.setScopesList(this.scopesList?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service
@ -158,6 +159,7 @@ export class ProviderOIDCComponent {
req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value);
req.setScopesList(this.scopesList?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service

View File

@ -1,5 +1,8 @@
@use '@angular/material' as mat;
@mixin identity-provider-theme($theme) {
$is-dark-theme: map-get($theme, is-dark);
$background: map-get($theme, background);
.identity-provider-desc {
font-size: 14px;
@ -36,10 +39,22 @@
}
}
.update-secret-checkbox {
margin: 0.5rem 0 0 0;
}
.formfield {
display: block;
max-width: 400px;
&.pwd {
display: none;
}
&.pwd.show {
display: block;
}
.name-hint {
font-size: 12px;
}
@ -59,6 +74,10 @@
}
}
.string-list-component-wrapper {
max-width: 400px;
}
.identity-provider-content {
display: flex;
flex-direction: column;

View File

@ -19,10 +19,10 @@ export class SettingsListComponent implements OnChanges {
constructor() {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.selectedId?.currentValue) {
if (changes['selectedId']?.currentValue) {
this.currentSetting =
this.settingsList && this.settingsList.find((l) => l.id === changes.selectedId.currentValue)
? changes.selectedId.currentValue
this.settingsList && this.settingsList.find((l) => l.id === changes['selectedId'].currentValue)
? changes['selectedId'].currentValue
: '';
} else {
this.currentSetting = this.settingsList ? this.settingsList[0].id : '';

View File

@ -1,30 +1,43 @@
<form class="string-list-form" (ngSubmit)="add(redInput)">
<cnsl-form-field class="formfield">
<cnsl-label>{{ title }}</cnsl-label>
<input #redInput cnslInput [formControl]="control" />
</cnsl-form-field>
<button
matTooltip="{{ 'ACTIONS.ADD' | translate }}"
type="submit"
mat-icon-button
[disabled]="control.invalid || control.disabled"
>
<mat-icon>add</mat-icon>
</button>
</form>
<div class="form-array-list">
<div class="form-field-list">
<div class="list-header-wrapper">
<p class="list-header cnsl-secondary-text">{{ title }}*</p>
<button
class="add-element-btn"
matTooltip="{{ 'ACTIONS.ADD' | translate }}"
type="button"
mat-icon-button
(click)="addArrayEntry()"
>
<mat-icon>add</mat-icon>
</button>
</div>
<ng-container *ngFor="let formControl of formArray.controls; index as i">
<div class="element-row">
<cnsl-form-field class="formfield" [hideRequiredMarker]="true">
<input cnslInput title="{{ 'IDP.SERVERS' | translate }}" [formControl]="$any(formControl)" required />
</cnsl-form-field>
<div class="string-list">
<div *ngFor="let str of value" class="value-line">
<span>{{ str }}</span>
<span class="fill-space"></span>
<button
type="button"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
mat-icon-button
(click)="remove(str)"
class="icon-button"
>
<mat-icon class="icon">cancel</mat-icon>
</button>
<button
class="add-element-btn"
[disabled]="i === 0 && formArray.controls.length === 1 && formControl.value === ''"
[matTooltip]="
i === 0 && formArray.controls.length === 1 ? ('ACTIONS.CLEAR' | translate) : ('ACTIONS.REMOVE' | translate)
"
type="button"
mat-icon-button
color="warn"
(click)="i === 0 && formArray.controls.length === 1 ? clearEntryAtIndex(i) : removeEntryAtIndex(i)"
>
<i *ngIf="i === 0 && formArray.controls.length === 1; else removeIcon" class="las la-times-circle"></i>
<ng-template #removeIcon>
<i class="las la-minus-circle"></i>
</ng-template>
</button>
</div>
</ng-container>
<span class="control-error" *ngIf="control.touched && control.errors && control.errors['errorsatleastone']">{{
control.errors['errorsatleastone'].i18nKey | translate
}}</span>
</div>
</div>

View File

@ -5,56 +5,50 @@
$background: map-get($theme, background);
$is-dark-theme: map-get($theme, is-dark);
$warn: map-get($theme, warn);
$warn-color: map-get($warn, 500);
$button-text-color: map-get($foreground, text);
$button-disabled-text-color: map-get($foreground, disabled-button);
$divider-color: map-get($foreground, dividers);
$secondary-text: map-get($foreground, secondary-text);
$warncolor: map-get($warn, 500);
.string-list {
width: 100%;
.form-array-list {
display: flex;
flex-direction: row;
max-width: 400px;
background: if($is-dark-theme, #00000020, mat.get-color-from-palette($background, cards));
margin-left: -1rem;
margin-right: -1rem;
padding: 0 1rem 0.5rem 1rem;
margin-top: 0.5rem;
.value-line {
.list-header-wrapper {
display: flex;
align-items: center;
margin: 0.5rem 0;
padding: 0 0 0 0.75rem;
border-radius: 4px;
background: map-get($background, infosection);
margin: 0.5rem -0.5rem 0 0;
.fill-space {
flex: 1;
.list-header {
font-size: 12px;
}
}
.icon-button {
height: 30px;
line-height: 30px;
.form-field-list {
flex: 1;
display: flex;
flex-direction: column;
.icon {
font-size: 1rem;
margin-bottom: 3px;
}
.element-row {
display: flex;
align-items: center;
&:not(:hover) {
color: $secondary-text;
.formfield {
flex: 1;
}
}
.control-error {
font-size: 12px;
color: $warncolor;
}
}
.add-element-btn {
margin-bottom: 0rem;
}
}
}
.string-list-form {
display: flex;
align-items: flex-end;
min-width: 320px;
.formfield {
width: 500px;
}
button {
margin-bottom: 0.9rem;
margin-right: -0.5rem;
}
}

View File

@ -1,7 +1,7 @@
import { Component, forwardRef, Input, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable, Subject, takeUntil } from 'rxjs';
import { requiredValidator } from '../form-field/validators/validators';
import { Component, forwardRef, Input, OnDestroy, ViewChildren, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, FormArray, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { distinctUntilChanged, Subject, takeUntil } from 'rxjs';
import { minArrayLengthValidator, requiredValidator } from '../form-field/validators/validators';
@Component({
selector: 'cnsl-string-list',
@ -16,22 +16,23 @@ import { requiredValidator } from '../form-field/validators/validators';
},
],
})
export class StringListComponent implements ControlValueAccessor, OnInit, OnDestroy {
export class StringListComponent implements ControlValueAccessor, OnDestroy {
@Input() title: string = '';
@Input() required: boolean = false;
@Input() public getValues: Observable<void> = new Observable(); // adds formfieldinput to array on emission
@Input() public control: FormControl = new FormControl<string>({ value: '', disabled: true });
@Input() public control: FormControl = new FormControl<string[]>({ value: [], disabled: true });
private destroy$: Subject<void> = new Subject();
@ViewChild('redInput') input!: any;
private val: string[] = [];
@ViewChildren('stringInput') input!: any[];
public val: string[] = [];
ngOnInit(): void {
this.getValues.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.add(this.input.nativeElement);
public formArray: FormArray = new FormArray([new FormControl('', [requiredValidator])]);
constructor() {
this.control.setValidators([minArrayLengthValidator(1)]);
this.formArray.valueChanges.pipe(takeUntil(this.destroy$), distinctUntilChanged()).subscribe((value) => {
this.value = value;
});
this.required ? this.control.setValidators([requiredValidator]) : this.control.setValidators([]);
}
ngOnDestroy(): void {
@ -50,12 +51,24 @@ export class StringListComponent implements ControlValueAccessor, OnInit, OnDest
}
}
addArrayEntry() {
this.formArray.push(new FormControl('', [requiredValidator]));
}
removeEntryAtIndex(index: number) {
this.formArray.removeAt(index);
}
clearEntryAtIndex(index: number) {
this.formArray.controls[index].setValue('');
}
get value() {
return this.val;
}
writeValue(value: string[]) {
this.value = value;
value.map((v, i) => this.formArray.setControl(i, new FormControl(v, [requiredValidator])));
}
registerOnChange(fn: any) {
@ -73,28 +86,4 @@ export class StringListComponent implements ControlValueAccessor, OnInit, OnDest
this.control.enable();
}
}
public add(input: any): void {
if (this.control.valid) {
const trimmed = input.value.trim();
if (trimmed) {
this.val ? this.val.push(input.value) : (this.val = [input.value]);
this.onChange(this.val);
this.onTouch(this.val);
}
if (input) {
input.value = '';
}
}
}
public remove(str: string): void {
const index = this.value.indexOf(str);
if (index >= 0) {
this.value.splice(index, 1);
this.onChange(this.value);
this.onTouch(this.value);
}
}
}

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
import { MatLegacyButtonModule } from '@angular/material/legacy-button';
import { MatLegacyChipsModule } from '@angular/material/legacy-chips';
import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { InputModule } from '../input/input.module';
@ -15,6 +16,7 @@ import { StringListComponent } from './string-list.component';
InputModule,
FormsModule,
ReactiveFormsModule,
MatLegacyChipsModule,
TranslateModule,
MatIconModule,
MatLegacyTooltipModule,

View File

@ -45,7 +45,7 @@ export class OrgCreateComponent {
public pwdForm?: UntypedFormGroup;
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = ['de', 'en', 'fr', 'it', 'ja', 'pl', 'zh'];
public languages: string[] = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh'];
public policy?: PasswordComplexityPolicy.AsObject;
public usePassword: boolean = false;

View File

@ -437,6 +437,7 @@
class="redirect-section"
[disabled]="false"
[(ngModel)]="redirectUris"
[ngModelOptions]="{ standalone: true }"
[getValues]="requestRedirectValuesSubject$"
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
@ -447,6 +448,7 @@
class="redirect-section"
[disabled]="false"
[(ngModel)]="postLogoutUrisList"
[ngModelOptions]="{ standalone: true }"
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[getValues]="requestRedirectValuesSubject$"
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"

View File

@ -398,10 +398,10 @@
<ng-container *ngIf="currentSetting === 'urls'">
<cnsl-card title=" {{ 'APP.URLS' | translate }}">
<cnsl-info-section *ngIf="environmentMap?.issuer">
<cnsl-info-section *ngIf="environmentMap['issuer']">
<div
[innerHtml]="
'APP.OIDC.WELLKNOWN' | translate : { url: environmentMap.issuer + '/.well-known/openid-configuration' }
'APP.OIDC.WELLKNOWN' | translate : { url: environmentMap['issuer'] + '/.well-known/openid-configuration' }
"
></div>
</cnsl-info-section>

View File

@ -34,7 +34,7 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
this.projectId = params.projectid;
this.projectId = params['projectid'];
const breadcrumbs = [
new Breadcrumb({

View File

@ -49,19 +49,19 @@ export class ProjectGrantDetailComponent {
private breadcrumbService: BreadcrumbService,
) {
this.route.params.subscribe((params) => {
this.projectid = params.projectid;
this.grantid = params.grantid;
this.projectid = params['projectid'];
this.grantid = params['grantid'];
this.dataSource = new ProjectGrantMembersDataSource(this.mgmtService);
this.dataSource.loadMembers(params.projectid, params.grantid, 0, this.INITIALPAGESIZE);
this.dataSource.loadMembers(params['projectid'], params['grantid'], 0, this.INITIALPAGESIZE);
this.getRoleOptions(params.projectid);
this.getRoleOptions(params['projectid']);
this.getMemberRoleOptions();
this.changePageFactory = (event?: PageEvent) => {
return this.dataSource.loadMembers(
params.projectid,
params.grantid,
params['projectid'],
params['grantid'],
event?.pageIndex ?? 0,
event?.pageSize ?? this.INITIALPAGESIZE,
);

View File

@ -24,7 +24,7 @@ export class ProjectsComponent {
breadcrumbService: BreadcrumbService,
) {
this.activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
const type = params.type;
const type = params['type'];
if (type && type === 'owned') {
this.setType(ProjectType.PROJECTTYPE_OWNED);
} else if (type && type === 'granted') {

View File

@ -10,11 +10,11 @@
<form *ngIf="userForm" [formGroup]="userForm" (ngSubmit)="createUser()" class="machine-create-form">
<div class="machine-create-content">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.MACHINE.USERNAME' | translate }}*</cnsl-label>
<cnsl-label>{{ 'USER.MACHINE.USERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="userName" required />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.MACHINE.NAME' | translate }}*</cnsl-label>
<cnsl-label>{{ 'USER.MACHINE.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" required />
</cnsl-form-field>
<cnsl-form-field class="formfield">

View File

@ -13,11 +13,11 @@
<div class="user-create-grid">
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}*</cnsl-label>
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}</cnsl-label>
<input cnslInput matRipple formControlName="email" required />
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}*</cnsl-label>
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}</cnsl-label>
<input
cnslInput
formControlName="userName"
@ -28,11 +28,11 @@
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}*</cnsl-label>
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="firstName" required />
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}*</cnsl-label>
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="lastName" required />
</cnsl-form-field>
<cnsl-form-field>

View File

@ -33,7 +33,7 @@ import {
export class UserCreateComponent implements OnInit, OnDestroy {
public user: AddHumanUserRequest.AsObject = new AddHumanUserRequest().toObject();
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = ['de', 'en', 'fr', 'it', 'ja', 'pl', 'zh'];
public languages: string[] = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh'];
public selected: CountryPhoneCode | undefined;
public countryPhoneCodes: CountryPhoneCode[] = [];
public userForm!: UntypedFormGroup;

View File

@ -33,7 +33,7 @@ import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.c
export class AuthUserDetailComponent implements OnDestroy {
public user?: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en', 'fr', 'it', 'ja', 'pl', 'zh'];
public languages: string[] = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh'];
private subscription: Subscription = new Subscription();

View File

@ -11,8 +11,8 @@ export class PhoneDetailComponent implements OnChanges {
public country: string | undefined;
ngOnChanges(changes: SimpleChanges): void {
if (changes.phone.currentValue) {
const phoneNumber = formatPhone(changes.phone.currentValue);
if (changes['phone'].currentValue) {
const phoneNumber = formatPhone(changes['phone'].currentValue);
if (this.phone !== phoneNumber.phone) {
this.phone = phoneNumber.phone;
this.country = phoneNumber.country;

View File

@ -44,7 +44,7 @@ export class UserDetailComponent implements OnInit {
public user!: User.AsObject;
public metadata: Metadata.AsObject[] = [];
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en', 'it', 'fr', 'ja', 'pl', 'zh'];
public languages: string[] = ['de', 'en', 'es', 'it', 'fr', 'ja', 'pl', 'zh'];
public ChangeType: any = ChangeType;

View File

@ -96,7 +96,7 @@ export class UserTableComponent implements OnInit {
ngOnInit(): void {
this.route.queryParams.pipe(take(1)).subscribe((params) => {
this.getData(this.INITIAL_PAGE_SIZE, 0, this.type);
if (params.deferredReload) {
if (params['deferredReload']) {
setTimeout(() => {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize, this.type);
}, 2000);

View File

@ -22,7 +22,7 @@ export class LocalizedDatePipe implements PipeTransform {
return moment(value).format(`${format}, HH:mm`);
}
} else {
const lang = ['de', 'en', 'fr', 'it', 'ja', 'pl', 'zh'].includes(this.translateService.currentLang)
const lang = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh'].includes(this.translateService.currentLang)
? this.translateService.currentLang
: 'en';
const datePipe: DatePipe = new DatePipe(lang);

View File

@ -1047,6 +1047,10 @@ export class AdminService {
return this.grpcService.admin.getProviderByID(req, null).then((resp) => resp.toObject());
}
public getProviderID(req: GetProviderByIDRequest): Promise<GetProviderByIDResponse> {
return this.grpcService.admin.getProviderByID(req, null);
}
public listIAMMembers(
limit: number,
offset: number,

View File

@ -987,6 +987,10 @@ export class ManagementService {
return this.grpcService.mgmt.getProviderByID(req, null).then((resp) => resp.toObject());
}
public getProviderID(req: GetProviderByIDRequest): Promise<GetProviderByIDResponse> {
return this.grpcService.mgmt.getProviderByID(req, null);
}
public addHumanUser(req: AddHumanUserRequest): Promise<AddHumanUserResponse.AsObject> {
return this.grpcService.mgmt.addHumanUser(req, null).then((resp) => resp.toObject());
}

View File

@ -1,8 +1,6 @@
import { Injectable } from '@angular/core';
import { Meta } from '@angular/platform-browser';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
@ -14,7 +12,7 @@ export class SeoService {
config = {
title: 'ZITADEL Console',
description: 'Managementplatform for ZITADEL',
image: 'https://www.zitadel.ch/zitadel-social-preview25.png',
image: 'https://www.zitadel.com/images/preview.png',
slug: '',
...config,
};
@ -27,15 +25,11 @@ export class SeoService {
if (config.image) {
this.meta.updateTag({ property: 'og:image', content: config.image });
}
this.meta.updateTag({
property: 'og:url',
content: `https://${environment.production ? 'console.zitadel.ch' : 'console.zitadel.dev'}/${config.slug}`,
});
this.meta.updateTag({ property: 'twitter:card', content: 'summary' });
this.meta.updateTag({ property: 'og:site', content: '@zitadel_ch' });
this.meta.updateTag({ property: 'og:title', content: config.title });
this.meta.updateTag({ property: 'og:image', content: 'https://www.zitadel.ch/zitadel-social-preview25.png' });
this.meta.updateTag({ property: 'og:image', content: 'https://www.zitadel.com/images/preview.png' });
this.meta.updateTag({ property: 'og:description', content: config.description });
}
}

View File

@ -247,6 +247,7 @@
},
"ERRORS": {
"REQUIRED": "Bitte fülle dieses Feld aus.",
"ATLEASTONE": "Geben Sie mindestens einen Wert an.",
"TOKENINVALID": {
"TITLE": "Du bist abgemeldet",
"DESCRIPTION": "Klicke auf \"Einloggen\", um Dich erneut anzumelden."
@ -1019,6 +1020,7 @@
"LANGUAGE": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1217,6 +1219,7 @@
"LOCALES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1697,6 +1700,7 @@
"2": "inaktiv"
},
"AZUREADTENANTTYPES": {
"3": "Tenant ID",
"0": "Common",
"1": "Organizations",
"2": "Consumers"
@ -2078,6 +2082,7 @@
"LANGUAGES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",

View File

@ -248,6 +248,7 @@
},
"ERRORS": {
"REQUIRED": "Please fill in this field.",
"ATLEASTONE": "Provide at least one value.",
"TOKENINVALID": {
"TITLE": "Your authorization token has expired.",
"DESCRIPTION": "Click the button below to log in again."
@ -1020,6 +1021,7 @@
"LANGUAGE": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1149,6 +1151,10 @@
"MAXSIZEEXCEEDED": "Maximum size of 524kB exceeded.",
"NOSVGSUPPORTED": "SVG are not supported!",
"FONTINLOGINONLY": "The font is currently only displayed in the login interface.",
"BACKGROUNDCOLOR": "Background color",
"PRIMARYCOLOR": "Primary color",
"WARNCOLOR": "Warning color",
"FONTCOLOR": "Font color",
"VIEWS": {
"PREVIEW": "Preview",
"CURRENT": "Current Configuration"
@ -1209,11 +1215,12 @@
"RESET_TITLE": "Restore Default Values",
"RESET_DESCRIPTION": "You are about to restore all default values. All changes you have made will be permanently deleted. Do you really want to continue?",
"UNSAVED_TITLE": "Continue without saving?",
"UNSAVED_DESCRIPTION": "Your have made changes without saving. Do you want to save now?",
"UNSAVED_DESCRIPTION": "You have made changes without saving. Do you want to save now?",
"LOCALE": "Locale Code",
"LOCALES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1693,6 +1700,7 @@
"2": "inactive"
},
"AZUREADTENANTTYPES": {
"3": "Tenant ID",
"0": "Common",
"1": "Organizations",
"2": "Consumers"
@ -2071,6 +2079,7 @@
"LANGUAGES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",

File diff suppressed because it is too large Load Diff

View File

@ -247,6 +247,7 @@
},
"ERRORS": {
"REQUIRED": "Remplis ce champ s'il te plaît.",
"ATLEASTONE": "Indiquez au moins une valeur.",
"TOKENINVALID": {
"TITLE": "Votre jeton d'autorisation a expiré.",
"DESCRIPTION": "Cliquez sur le bouton ci-dessous pour vous reconnecter."
@ -1019,6 +1020,7 @@
"LANGUAGE": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1217,6 +1219,7 @@
"LOCALES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1701,6 +1704,7 @@
"2": "inactif"
},
"AZUREADTENANTTYPES": {
"3": "Tenant ID",
"0": "Common",
"1": "Organizations",
"2": "Consumers"
@ -2067,6 +2071,7 @@
"LANGUAGES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",

View File

@ -247,6 +247,7 @@
},
"ERRORS": {
"REQUIRED": "Compilare questo campo.",
"ATLEASTONE": "Inserisci almeno un valore.",
"TOKENINVALID": {
"TITLE": "Il tuo Access Token \u00e8 scaduto.",
"DESCRIPTION": "Clicca il pulsante per richiedere una nuova sessione."
@ -1020,6 +1021,7 @@
"LANGUAGE": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1218,6 +1220,7 @@
"LOCALES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1702,6 +1705,7 @@
"2": "inattivo"
},
"AZUREADTENANTTYPES": {
"3": "Tenant ID",
"0": "Common",
"1": "Organizations",
"2": "Consumers"
@ -2080,6 +2084,7 @@
"LANGUAGES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",

View File

@ -248,6 +248,7 @@
},
"ERRORS": {
"REQUIRED": "一部の必須項目が不足しています。",
"ATLEASTONE": "少なくとも 1 つの値を指定してください。",
"TOKENINVALID": {
"TITLE": "トークンが期限切れになりました。",
"DESCRIPTION": "下のボタンをクリックして、もう一度ログインする。"
@ -1020,6 +1021,7 @@
"LANGUAGE": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1213,6 +1215,7 @@
"LOCALES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1692,9 +1695,10 @@
"2": "非アクティブ"
},
"AZUREADTENANTTYPES": {
"0": "共通",
"1": "組織",
"2": "顧客"
"3": "Tenant ID",
"0": "Common",
"1": "Organizations",
"2": "Consumers"
},
"AZUREADTENANTTYPE": "テナントタイプ",
"AZUREADTENANTID": "テナントID",
@ -2070,6 +2074,7 @@
"LANGUAGES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",

View File

@ -247,6 +247,7 @@
},
"ERRORS": {
"REQUIRED": "Proszę wypełnić to pole.",
"ATLEASTONE": "Podaj co najmniej jedną wartość.",
"TOKENINVALID": {
"TITLE": "Twój token autoryzacji wygasł.",
"DESCRIPTION": "Kliknij przycisk poniżej, aby ponownie się zalogować."
@ -1019,6 +1020,7 @@
"LANGUAGE": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1217,6 +1219,7 @@
"LOCALES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1701,6 +1704,7 @@
"2": "nieaktywny"
},
"AZUREADTENANTTYPES": {
"3": "Tenant ID",
"0": "Common",
"1": "Organizations",
"2": "Consumers"
@ -2079,6 +2083,7 @@
"LANGUAGES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",

View File

@ -247,6 +247,7 @@
},
"ERRORS": {
"REQUIRED": "请填写此栏",
"ATLEASTONE": "P至少提供一个值。",
"TOKENINVALID": {
"TITLE": "您的授权令牌已过期。",
"DESCRIPTION": "点击下方按钮再次登录。"
@ -1019,6 +1020,7 @@
"LANGUAGE": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1216,6 +1218,7 @@
"LOCALES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",
@ -1700,6 +1703,7 @@
"2": "停用"
},
"AZUREADTENANTTYPES": {
"3": "Tenant ID",
"0": "Common",
"1": "Organizations",
"2": "Consumers"
@ -2066,6 +2070,7 @@
"LANGUAGES": {
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"it": "Italiano",
"ja": "日本語",

View File

@ -1,3 +0,0 @@
export const environment = {
production: true,
};

View File

@ -1,16 +0,0 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

View File

@ -1,15 +1,6 @@
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/xml/xml';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)

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