mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 17:43:08 +00:00
chore: merge (#5713)
```[tasklist] ### Definition of Ready - [ ] I am happy with the code - [ ] Short description of the feature/issue is added in the pr description - [ ] PR is linked to the corresponding user story - [ ] Acceptance criteria are met - [ ] All open todos and follow ups are defined in a new ticket and justified - [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented. - [ ] No debug or dead code - [ ] Critical parts are tested automatically - [ ] Where possible E2E tests are implemented - [ ] Documentation/examples are up-to-date - [ ] All non-functional requirements are met - [ ] Functionality of the acceptance criteria is checked manually on the dev system. ```
This commit is contained in:
2
.github/workflows/test-code.yml
vendored
2
.github/workflows/test-code.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: linting
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.50.1
|
||||
version: v1.52
|
||||
only-new-issues: true
|
||||
skip-pkg-cache: true
|
||||
- name: Publish go coverage
|
||||
|
10
.github/workflows/zitadel.yml
vendored
10
.github/workflows/zitadel.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v1.11.0
|
||||
args: release
|
||||
args: release --timeout 50m
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_TOKEN_TAP: ${{ steps.generate-token.outputs.token }}
|
||||
@@ -78,3 +78,11 @@ jobs:
|
||||
with:
|
||||
file: .artifacts/codecov/profile.cov
|
||||
name: go-codecov
|
||||
- name: Bump Chart Version
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: zitadel/zitadel-charts
|
||||
event-type: zitadel-released
|
||||
client-payload: '{"semanticoutputs": "${{ steps.semantic.outputs }}"}'
|
||||
|
@@ -1,8 +1,7 @@
|
||||
module.exports = {
|
||||
branches: [
|
||||
{name: 'main'},
|
||||
{name: 'next'},
|
||||
{name: '1.87.x', range: '1.87.x', channel: '1.87.x'}
|
||||
{name: 'next'}
|
||||
],
|
||||
plugins: [
|
||||
"@semantic-release/commit-analyzer"
|
||||
|
32
README.md
32
README.md
@@ -35,7 +35,7 @@ Do you have project that requires a multi-tenant user management with self-servi
|
||||
Look no further — ZITADEL combines the ease of Auth0 with the versatility of Keycloak.
|
||||
|
||||
We provide you with a wide range of out of the box features to accelerate your project.
|
||||
Multi-tenancy with branding customization, secure login, self-service, OpenID Connect, OAuth2.x, SAML2, Passwordless with FIDO2 (including Passkeys), OTP, U2F, and an unlimited audit trail is there for you, ready to use.
|
||||
Multi-tenancy with branding customization, secure login, self-service, OpenID Connect, OAuth2.x, SAML2, LDAP, Passwordless with FIDO2 (including Passkeys), OTP, U2F, and an unlimited audit trail is there for you, ready to use.
|
||||
|
||||
With ZITADEL you can rely on a hardened and extensible turnkey solution to solve all of your authentication and authorization needs.
|
||||
|
||||
@@ -90,20 +90,34 @@ Yet it offers everything you need for a customer identity ([CIAM](https://zitade
|
||||
|
||||
## Features
|
||||
|
||||
Authentication
|
||||
- Single Sign On (SSO)
|
||||
- Passwordless with FIDO2 support (Including Passkeys)
|
||||
- Username / Password
|
||||
- Multifactor authentication with OTP, U2F
|
||||
- [Identity Brokering](https://zitadel.com/docs/guides/integrate/identity-brokering)
|
||||
- [Machine-to-machine (JWT profile)](https://zitadel.com/docs/guides/integrate/serviceusers)
|
||||
- Personal Access Tokens (PAT)
|
||||
- Role Based Access Control (RBAC)
|
||||
- [Delegate role management to third-parties](https://zitadel.com/docs/guides/manage/console/projects)
|
||||
- [Self-registration](https://zitadel.com/docs/concepts/features/selfservice#registration) including verification
|
||||
- [Self-service](https://zitadel.com/docs/concepts/features/selfservice) for end-users, business customers, and administrators
|
||||
- Multifactor authentication with OTP, U2F, SMS
|
||||
- LDAP
|
||||
- [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints)
|
||||
- [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://zitadel.com/docs/apis/saml/endpoints)
|
||||
- [Machine-to-machine](https://zitadel.com/docs/guides/integrate/serviceusers) with JWT profile, Personal Access Tokens (PAT), and Client Credentials
|
||||
|
||||
Multi-Tenancy
|
||||
- [Identity Brokering](https://zitadel.com/docs/guides/integrate/identity-brokering) with templates for popular identity providers
|
||||
- [Delegate role management to third-parties](https://zitadel.com/docs/guides/manage/console/projects)
|
||||
- Domain discovery
|
||||
|
||||
Integration
|
||||
- [GRPC and REST APIs](https://zitadel.com/docs/apis/introduction)
|
||||
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to call any API, send webhooks, adjust workflows, or customize tokens
|
||||
- Role Based Access Control (RBAC)
|
||||
|
||||
Self-Service
|
||||
- [Self-registration](https://zitadel.com/docs/concepts/features/selfservice#registration) including verification
|
||||
- [Self-service](https://zitadel.com/docs/concepts/features/selfservice) for end-users, business customers, and administrators
|
||||
- [Administration UI (Console)](https://zitadel.com/docs/guides/manage/console/overview)
|
||||
|
||||
Deployment
|
||||
- [Postgres](https://zitadel.com/docs/self-hosting/manage/database#postgres) (version >= 14) or [CockroachDB](https://zitadel.com/docs/self-hosting/manage/database#cockroach) (version >= 22.0)
|
||||
- [Zero Downtime Updates](https://zitadel.com/docs/concepts/architecture/solution#zero-downtime-updates)
|
||||
|
||||
Track upcoming features on our [roadmap](https://zitadel.com/roadmap).
|
||||
|
||||
|
@@ -93,4 +93,36 @@ protoc \
|
||||
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 \
|
||||
--grpc-gateway_opt logtostderr=true \
|
||||
--grpc-gateway_opt allow_delete_body=true \
|
||||
--openapiv2_out ${OPENAPI_PATH} \
|
||||
--openapiv2_opt logtostderr=true \
|
||||
--openapiv2_opt allow_delete_body=true \
|
||||
--authoption_out=${GRPC_PATH}/user \
|
||||
--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 \
|
||||
--grpc-gateway_opt logtostderr=true \
|
||||
--grpc-gateway_opt allow_delete_body=true \
|
||||
--openapiv2_out ${OPENAPI_PATH} \
|
||||
--openapiv2_opt logtostderr=true \
|
||||
--openapiv2_opt allow_delete_body=true \
|
||||
--authoption_out=${GRPC_PATH}/session \
|
||||
--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"
|
||||
|
@@ -554,7 +554,7 @@ DefaultInstance:
|
||||
Title: Zitadel - User initialisieren
|
||||
PreHeader: User initialisieren
|
||||
Subject: User initialisieren
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hallo {{.DisplayName}},
|
||||
Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Mit dem Benutzernamen <br><strong>{{.PreferredLoginName}}</strong><br> kannst du dich anmelden. Nutze den untenstehenden Button, um die Initialisierung abzuschliessen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.
|
||||
ButtonText: Initialisierung abschliessen
|
||||
- MessageTextType: PasswordReset
|
||||
@@ -562,7 +562,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Passwort zurücksetzen
|
||||
PreHeader: Passwort zurücksetzen
|
||||
Subject: Passwort zurücksetzen
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hallo {{.DisplayName}},
|
||||
Text: Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren.
|
||||
ButtonText: Passwort zurücksetzen
|
||||
- MessageTextType: VerifyEmail
|
||||
@@ -570,7 +570,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Email verifizieren
|
||||
PreHeader: Email verifizieren
|
||||
Subject: Email verifizieren
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hallo {{.DisplayName}},
|
||||
Text: Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren <br>(Code <strong>{{.Code}}</strong>).<br> Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren.
|
||||
ButtonText: Email verifizieren
|
||||
- MessageTextType: VerifyPhone
|
||||
@@ -578,7 +578,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Telefonnummer verifizieren
|
||||
PreHeader: Telefonnummer verifizieren
|
||||
Subject: Telefonnummer verifizieren
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hallo {{.DisplayName}},
|
||||
Text: Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst (Code {{.Code}})
|
||||
ButtonText: Telefon verifizieren
|
||||
- MessageTextType: DomainClaimed
|
||||
@@ -586,7 +586,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Domain wurde beansprucht
|
||||
PreHeader: Email / Username ändern
|
||||
Subject: Domain wurde beansprucht
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hallo {{.DisplayName}},
|
||||
Text: Die Domain {{.Domain}} wurde von einer Organisation beansprucht. Dein derzeitiger User {{.Username}} ist nicht Teil dieser Organisation. Daher musst du beim nächsten Login eine neue Email hinterlegen. Für diesen Login haben wir dir einen temporären Usernamen ({{.TempUsername}}) erstellt.
|
||||
ButtonText: Login
|
||||
- MessageTextType: PasswordChange
|
||||
@@ -594,7 +594,7 @@ DefaultInstance:
|
||||
Title: ZITADEL - Passwort von Benutzer wurde geändert
|
||||
PreHeader: Passwort Änderung
|
||||
Subject: Passwort von Benutzer wurde geändert
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hallo {{.DisplayName}},
|
||||
Text: Das Password vom Benutzer wurde geändert. Wenn diese Änderung von jemand anderem gemacht wurde, empfehlen wir die sofortige Zurücksetzung ihres Passworts.
|
||||
ButtonText: Login
|
||||
- MessageTextType: InitCode
|
||||
@@ -602,7 +602,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Initialize User
|
||||
PreHeader: Initialize User
|
||||
Subject: Initialize User
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hello {{.DisplayName}},
|
||||
Text: This user was created in Zitadel. Use the username {{.PreferredLoginName}} to login. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
|
||||
ButtonText: Finish initialization
|
||||
- MessageTextType: PasswordReset
|
||||
@@ -610,7 +610,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Reset password
|
||||
PreHeader: Reset password
|
||||
Subject: Reset password
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hello {{.DisplayName}},
|
||||
Text: We received a password reset request. Please use the button below to reset your password. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
|
||||
ButtonText: Reset password
|
||||
- MessageTextType: VerifyEmail
|
||||
@@ -618,7 +618,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Verify email
|
||||
PreHeader: Verify email
|
||||
Subject: Verify email
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hello {{.DisplayName}},
|
||||
Text: A new email has been added. Please use the button below to verify your mail. (Code {{.Code}}) If you din't add a new email, please ignore this email.
|
||||
ButtonText: Verify email
|
||||
- MessageTextType: VerifyPhone
|
||||
@@ -626,7 +626,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Verify phone
|
||||
PreHeader: Verify phone
|
||||
Subject: Verify phone
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hello {{.DisplayName}},
|
||||
Text: A new phonenumber has been added. Please use the following code to verify it {{.Code}}.
|
||||
ButtonText: Verify phone
|
||||
- MessageTextType: DomainClaimed
|
||||
@@ -634,7 +634,7 @@ DefaultInstance:
|
||||
Title: Zitadel - Domain has been claimed
|
||||
PreHeader: Change email / username
|
||||
Subject: Domain has been claimed
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hello {{.DisplayName}},
|
||||
Text: The domain {{.Domain}} has been claimed by an organisation. Your current user {{.UserName}} is not part of this organisation. Therefore you'll have to change your email when you login. We have created a temporary username ({{.TempUsername}}) for this login.
|
||||
ButtonText: Login
|
||||
- MessageTextType: PasswordChange
|
||||
@@ -642,7 +642,7 @@ DefaultInstance:
|
||||
Title: ZITADEL - Password of user has changed
|
||||
PreHeader: Change password
|
||||
Subject: Password of user has changed
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Greeting: Hello {{.DisplayName}},
|
||||
Text: The password of your user has changed. If this change was not done by you, please be advised to immediately reset your password.
|
||||
ButtonText: Login
|
||||
|
||||
|
53
cmd/setup/10.go
Normal file
53
cmd/setup/10.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 10.sql
|
||||
correctCreationDate10 string
|
||||
)
|
||||
|
||||
type CorrectCreationDate struct {
|
||||
dbClient *database.DB
|
||||
}
|
||||
|
||||
func (mig *CorrectCreationDate) Execute(ctx context.Context) (err error) {
|
||||
tx, err := mig.dbClient.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mig.dbClient.Type() == "cockroach" {
|
||||
if _, err := tx.Exec("SET experimental_enable_temp_tables=on"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logging.OnError(tx.Rollback()).Debug("rollback failed")
|
||||
return
|
||||
}
|
||||
err = tx.Commit()
|
||||
}()
|
||||
for {
|
||||
res, err := tx.ExecContext(ctx, correctCreationDate10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, _ := res.RowsAffected()
|
||||
logging.WithFields("count", affected).Info("creation dates changed")
|
||||
if affected == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mig *CorrectCreationDate) String() string {
|
||||
return "10_correct_creation_date"
|
||||
}
|
28
cmd/setup/10.sql
Normal file
28
cmd/setup/10.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
CREATE temporary TABLE IF NOT EXISTS wrong_events (
|
||||
instance_id STRING
|
||||
, event_sequence INT8
|
||||
, current_cd TIMESTAMPTZ
|
||||
, next_cd TIMESTAMPTZ
|
||||
);
|
||||
|
||||
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
|
||||
) WHERE
|
||||
current_cd < next_cd
|
||||
ORDER BY
|
||||
event_sequence DESC
|
||||
);
|
||||
|
||||
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;
|
@@ -56,15 +56,16 @@ func MustNewConfig(v *viper.Viper) *Config {
|
||||
}
|
||||
|
||||
type Steps struct {
|
||||
s1ProjectionTable *ProjectionTable
|
||||
s2AssetsTable *AssetTable
|
||||
FirstInstance *FirstInstance
|
||||
s4EventstoreIndexes *EventstoreIndexesNew
|
||||
s5LastFailed *LastFailed
|
||||
s6OwnerRemoveColumns *OwnerRemoveColumns
|
||||
s7LogstoreTables *LogstoreTables
|
||||
s8AuthTokens *AuthTokenIndexes
|
||||
s9EventstoreIndexes2 *EventstoreIndexesNew
|
||||
s1ProjectionTable *ProjectionTable
|
||||
s2AssetsTable *AssetTable
|
||||
FirstInstance *FirstInstance
|
||||
s4EventstoreIndexes *EventstoreIndexesNew
|
||||
s5LastFailed *LastFailed
|
||||
s6OwnerRemoveColumns *OwnerRemoveColumns
|
||||
s7LogstoreTables *LogstoreTables
|
||||
s8AuthTokens *AuthTokenIndexes
|
||||
s9EventstoreIndexes2 *EventstoreIndexesNew
|
||||
s10EventstoreCreationDate *CorrectCreationDate
|
||||
}
|
||||
|
||||
type encryptionKeyConfig struct {
|
||||
|
@@ -88,6 +88,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.s10EventstoreCreationDate = &CorrectCreationDate{dbClient: dbClient}
|
||||
|
||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil)
|
||||
logging.OnError(err).Fatal("unable to start projections")
|
||||
@@ -123,6 +124,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.s10EventstoreCreationDate)
|
||||
logging.OnError(err).Fatal("unable to migrate step 10")
|
||||
|
||||
for _, repeatableStep := range repeatableSteps {
|
||||
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)
|
||||
|
@@ -33,7 +33,9 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/session/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/system"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/user/v2"
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/api/oidc"
|
||||
@@ -223,7 +225,10 @@ func startAPIs(
|
||||
logging.Warn("access logs are currently in beta")
|
||||
}
|
||||
accessInterceptor := middleware.NewAccessInterceptor(accessSvc, config.Quotas.Access)
|
||||
apis := api.New(config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, accessSvc)
|
||||
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, accessSvc)
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting auth repo: %w", err)
|
||||
@@ -244,9 +249,15 @@ 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 {
|
||||
return err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, session.CreateServer(commands, queries)); err != nil {
|
||||
return err
|
||||
}
|
||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
||||
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
||||
apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle))
|
||||
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle))
|
||||
|
||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources)
|
||||
if err != nil {
|
||||
@@ -258,37 +269,34 @@ func startAPIs(
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start openapi handler: %w", err)
|
||||
}
|
||||
apis.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
|
||||
apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler)
|
||||
|
||||
oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, accessInterceptor.Handle)
|
||||
oidcProvider, err := oidc.NewProvider(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, accessInterceptor.Handle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start oidc provider: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerPrefixes(oidcProvider.HttpHandler(), "/.well-known/openid-configuration", "/oidc/v1", "/oauth/v2")
|
||||
|
||||
samlProvider, err := saml.NewProvider(ctx, config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, accessInterceptor.Handle)
|
||||
samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, accessInterceptor.Handle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start saml provider: %w", err)
|
||||
}
|
||||
apis.RegisterHandler(saml.HandlerPrefix, samlProvider.HttpHandler())
|
||||
apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler())
|
||||
|
||||
c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, accessInterceptor.Handle, config.CustomerPortal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start console: %w", err)
|
||||
}
|
||||
apis.RegisterHandler(console.HandlerPrefix, c)
|
||||
apis.RegisterHandlerOnPrefix(console.HandlerPrefix, c)
|
||||
|
||||
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, console.HandlerPrefix+"/", op.AuthCallbackURL(oidcProvider), provider.AuthCallbackURL(samlProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, provider.NewIssuerInterceptor(samlProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start login: %w", err)
|
||||
}
|
||||
apis.RegisterHandler(login.HandlerPrefix, l.Handler())
|
||||
apis.RegisterHandlerOnPrefix(login.HandlerPrefix, l.Handler())
|
||||
|
||||
//handle oidc at last, to be able to handle the root
|
||||
//we might want to change that in the future
|
||||
//esp. if we want to have multiple well-known endpoints
|
||||
//it might make sense to handle the discovery endpoint and oauth and oidc prefixes individually
|
||||
//but this will require a change in the oidc lib
|
||||
apis.RegisterHandler("", oidcProvider.HttpHandler())
|
||||
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
|
||||
apis.RouteGRPC()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -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'.
|
@@ -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,16 @@
|
||||
"@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 +52,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 +86,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 +99,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
4793
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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']);
|
||||
|
@@ -1,16 +1,12 @@
|
||||
<div class="accounts-card">
|
||||
<cnsl-avatar
|
||||
(click)="editUserProfile()"
|
||||
*ngIf="user && user.human?.profile && user.human?.profile?.displayName"
|
||||
*ngIf="user"
|
||||
class="avatar"
|
||||
[ngClass]="{ 'iam-user': iamuser }"
|
||||
[forColor]="user.preferredLoginName"
|
||||
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
|
||||
[name]="
|
||||
user.human && user.human.profile && user.human.profile.displayName
|
||||
? user.human.profile.displayName
|
||||
: user.human?.profile?.firstName + ' ' + user.human?.profile?.lastName
|
||||
"
|
||||
[name]="user.human?.profile?.displayName ?? ''"
|
||||
[size]="80"
|
||||
>
|
||||
</cnsl-avatar>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -13,10 +13,10 @@
|
||||
data-e2e="member-avatar"
|
||||
>
|
||||
<cnsl-avatar
|
||||
*ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
|
||||
*ngIf="member && member.userType === UserType.TYPE_HUMAN; else cog"
|
||||
class="contributor-avatar dontcloseonclick"
|
||||
[avatarUrl]="member.avatarUrl || ''"
|
||||
[name]="member.displayName ? member.displayName : member.firstName + ' ' + member.lastName"
|
||||
[name]="member.displayName"
|
||||
[forColor]="member.preferredLoginName"
|
||||
[size]="32"
|
||||
>
|
||||
|
@@ -2,6 +2,7 @@ import { animate, animateChild, keyframes, query, stagger, style, transition, tr
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
|
||||
import { Type } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-contributors',
|
||||
@@ -36,6 +37,8 @@ export class ContributorsComponent {
|
||||
@Output() showDetailClicked: EventEmitter<void> = new EventEmitter();
|
||||
@Output() refreshClicked: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
public UserType: any = Type;
|
||||
|
||||
public emitAddMember(): void {
|
||||
this.addClicked.emit();
|
||||
}
|
||||
|
@@ -1,3 +1,33 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@mixin app-create-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$primary-color: mat.get-color-from-palette($primary, 500);
|
||||
|
||||
// Number of steps creating app
|
||||
$steps: 3;
|
||||
|
||||
.app-create-wrapper {
|
||||
// Reference: https://github.com/angular/components/issues/10681#issuecomment-695185806
|
||||
@for $i from 1 through $steps {
|
||||
@for $j from 1 through $i {
|
||||
.last-edited-step-#{$i}
|
||||
.mat-horizontal-stepper-header-container
|
||||
.mat-step-header:nth-child(#{1 + 2 * ($j - 1)})::after,
|
||||
.last-edited-step-#{$i}
|
||||
.mat-horizontal-stepper-header-container
|
||||
.mat-stepper-horizontal-line:nth-child(#{2 + 2 * ($j - 1)}),
|
||||
.last-edited-step-#{$i}
|
||||
.mat-horizontal-stepper-header-container
|
||||
.mat-step-header:nth-child(#{3 + 2 * ($j - 1)})::before {
|
||||
border-top-width: 3px;
|
||||
border-top-color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.create-layout-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@@ -5,6 +5,7 @@
|
||||
(click)="showFilter = !showFilter"
|
||||
class="cnsl-action-button"
|
||||
#triggereventfilter="cdkOverlayOrigin"
|
||||
data-e2e="open-filter-button"
|
||||
>
|
||||
<i class="las la-filter no-margin"></i>
|
||||
<span>{{ 'ACTIONS.FILTER' | translate }}</span>
|
||||
@@ -15,8 +16,6 @@
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[flexibleDimensions]="true"
|
||||
[lockPosition]="true"
|
||||
[cdkConnectedOverlayOffsetY]="10"
|
||||
[cdkConnectedOverlayPositions]="positions"
|
||||
[cdkConnectedOverlayOrigin]="triggereventfilter"
|
||||
@@ -29,7 +28,7 @@
|
||||
<div class="filter-events-top">
|
||||
<button mat-stroked-button type="button" (click)="reset()">{{ 'ACTIONS.RESET' | translate }}</button>
|
||||
<span class="filter-middle">{{ 'FILTER.TITLE' | translate }}</span>
|
||||
<button mat-raised-button color="primary" type="button" (click)="finish()">
|
||||
<button mat-raised-button color="primary" type="button" (click)="finish()" data-e2e="filter-finish-button">
|
||||
{{ 'ACTIONS.FINISH' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -96,6 +95,7 @@
|
||||
name="eventTypesFilterSet"
|
||||
class="cb"
|
||||
formControlName="eventTypesFilterSet"
|
||||
data-e2e="event-type-filter-checkbox"
|
||||
>{{ 'IAM.EVENTS.FILTERS.TYPE.CHECKBOX' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
@@ -22,5 +22,28 @@
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="name-query">
|
||||
<mat-checkbox
|
||||
id="state"
|
||||
class="cb"
|
||||
[checked]="getSubFilter(SubQuery.STATE)"
|
||||
(change)="changeCheckbox(SubQuery.STATE, $event)"
|
||||
>{{ 'FILTER.STATE' | translate }}
|
||||
</mat-checkbox>
|
||||
<div class="subquery" *ngIf="getSubFilter(SubQuery.STATE) as sq">
|
||||
<span class="nomethod cnsl-secondary-text">
|
||||
{{ 'FILTER.METHODS.1' | translate }}
|
||||
</span>
|
||||
|
||||
<cnsl-form-field class="filter-select-value">
|
||||
<mat-select [value]="sq.getState()" (selectionChange)="setValue(SubQuery.STATE, sq, $event)">
|
||||
<mat-option *ngFor="let state of states" [value]="state">
|
||||
{{ 'USER.STATE.' + state | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</cnsl-filter>
|
||||
|
@@ -3,13 +3,14 @@ import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { take } from 'rxjs';
|
||||
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
|
||||
import { OrgNameQuery, OrgQuery, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { OrgNameQuery, OrgQuery, OrgState, OrgStateQuery } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { UserNameQuery } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
|
||||
import { FilterComponent } from '../filter/filter.component';
|
||||
|
||||
enum SubQuery {
|
||||
NAME,
|
||||
STATE,
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -21,9 +22,9 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
|
||||
public SubQuery: any = SubQuery;
|
||||
public searchQueries: OrgQuery[] = [];
|
||||
|
||||
public states: OrgState[] = [OrgState.ORG_STATE_ACTIVE, OrgState.ORG_STATE_INACTIVE];
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -37,13 +38,17 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
|
||||
const orgQueries = filters.map((filter) => {
|
||||
if (filter.nameQuery) {
|
||||
const orgQuery = new OrgQuery();
|
||||
|
||||
const orgNameQuery = new OrgNameQuery();
|
||||
orgNameQuery.setName(filter.nameQuery.name);
|
||||
orgNameQuery.setMethod(filter.nameQuery.method);
|
||||
|
||||
orgQuery.setNameQuery(orgNameQuery);
|
||||
return orgQuery;
|
||||
} else if (filter.stateQuery) {
|
||||
const orgQuery = new OrgQuery();
|
||||
const orgStateQuery = new OrgStateQuery();
|
||||
orgStateQuery.setState(filter.stateQuery.state);
|
||||
orgQuery.setStateQuery(orgStateQuery);
|
||||
return orgQuery;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
@@ -64,12 +69,17 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
|
||||
const nq = new OrgNameQuery();
|
||||
nq.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
nq.setName('');
|
||||
|
||||
const oq = new OrgQuery();
|
||||
oq.setNameQuery(nq);
|
||||
|
||||
this.searchQueries.push(oq);
|
||||
break;
|
||||
case SubQuery.STATE:
|
||||
const sq = new OrgStateQuery();
|
||||
sq.setState(OrgState.ORG_STATE_ACTIVE);
|
||||
const osq = new OrgQuery();
|
||||
osq.setStateQuery(sq);
|
||||
this.searchQueries.push(osq);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (subquery) {
|
||||
@@ -79,6 +89,12 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
|
||||
this.searchQueries.splice(index_dn, 1);
|
||||
}
|
||||
break;
|
||||
case SubQuery.STATE:
|
||||
const index_sn = this.searchQueries.findIndex((q) => (q as OrgQuery).toObject().stateQuery !== undefined);
|
||||
if (index_sn > -1) {
|
||||
this.searchQueries.splice(index_sn, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +106,10 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
|
||||
(query as OrgNameQuery).setName(value);
|
||||
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
|
||||
break;
|
||||
case SubQuery.STATE:
|
||||
(query as OrgStateQuery).setState(value);
|
||||
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +122,13 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
case SubQuery.STATE:
|
||||
const sn = this.searchQueries.find((q) => (q as OrgQuery).toObject().stateQuery !== undefined);
|
||||
if (sn) {
|
||||
return (sn as OrgQuery).getStateQuery();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -15,8 +15,6 @@
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[flexibleDimensions]="true"
|
||||
[lockPosition]="true"
|
||||
[cdkConnectedOverlayOffsetY]="10"
|
||||
[cdkConnectedOverlayPositions]="positions"
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
|
@@ -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>
|
||||
|
@@ -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.
|
||||
|
@@ -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;
|
||||
|
@@ -110,8 +110,6 @@
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
[flexibleDimensions]="true"
|
||||
[lockPosition]="true"
|
||||
[cdkConnectedOverlayOffsetY]="10"
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[cdkConnectedOverlayPositions]="positions"
|
||||
@@ -197,9 +195,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ng-container
|
||||
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
|
||||
>
|
||||
<ng-container *ngIf="user">
|
||||
<div class="account-card-wrapper">
|
||||
<button
|
||||
cdkOverlayOrigin
|
||||
@@ -214,11 +210,7 @@
|
||||
[active]="showAccount"
|
||||
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
|
||||
[forColor]="user.preferredLoginName || ''"
|
||||
[name]="
|
||||
user.human?.profile?.displayName
|
||||
? user.human?.profile?.displayName ?? ''
|
||||
: user.human?.profile?.firstName + ' ' + user.human?.profile?.lastName
|
||||
"
|
||||
[name]="user.human?.profile?.displayName ?? ''"
|
||||
[size]="38"
|
||||
>
|
||||
</cnsl-avatar>
|
||||
@@ -227,8 +219,6 @@
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="accounttrigger"
|
||||
[flexibleDimensions]="true"
|
||||
[lockPosition]="true"
|
||||
[cdkConnectedOverlayOffsetY]="10"
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[cdkConnectedOverlayPositions]="accountCardPositions"
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@
|
||||
[checked]="selection.isSelected(row)"
|
||||
>
|
||||
<cnsl-avatar
|
||||
*ngIf="row?.displayName && row.firstName && row.lastName; else cog"
|
||||
*ngIf="row?.userType === UserType.TYPE_HUMAN; else cog"
|
||||
class="avatar"
|
||||
[name]="row.displayName"
|
||||
[avatarUrl]="row.avatarUrl || ''"
|
||||
|
@@ -10,6 +10,7 @@ import { ProjectGrantMembersDataSource } from 'src/app/pages/projects/owned-proj
|
||||
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
|
||||
import { getMembershipColor } from 'src/app/utils/color';
|
||||
|
||||
import { Type } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { AddMemberRolesDialogComponent } from '../add-member-roles-dialog/add-member-roles-dialog.component';
|
||||
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
|
||||
import { ProjectMembersDataSource } from '../project-members/project-members-datasource';
|
||||
@@ -43,6 +44,7 @@ export class MembersTableComponent implements OnInit, OnDestroy {
|
||||
|
||||
private destroyed: Subject<void> = new Subject();
|
||||
public displayedColumns: string[] = ['select', 'userId', 'displayName', 'loginname', 'email', 'roles'];
|
||||
public UserType: any = Type;
|
||||
|
||||
constructor(private dialog: MatDialog) {
|
||||
this.selection.changed.pipe(takeUntil(this.destroyed)).subscribe((_) => {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -189,6 +189,7 @@
|
||||
border: none;
|
||||
padding: 2px;
|
||||
border-radius: 50vw;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: if($is-dark-theme, #ffffff, $primary-color);
|
||||
|
@@ -72,6 +72,7 @@
|
||||
margin-right: 1rem;
|
||||
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
|
||||
box-shadow: 0 0 3px #0000001a;
|
||||
border: 1px solid rgba(#8795a1, 0.2);
|
||||
|
||||
i {
|
||||
font-size: 1rem;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
<div class="onboarding-header">
|
||||
<h1 class="title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
|
||||
|
||||
<p class="desc cnsl-secondary-text">{{ 'ONBOARDING.DESCRIPTION' | translate }}</p>
|
||||
<h2 class="desc">{{ 'ONBOARDING.DESCRIPTION' | translate }}</h2>
|
||||
|
||||
<ng-container *ngIf="!adminService.hideOnboarding && (adminService.progressAllDone | async) === false">
|
||||
<div class="onboarding-progress-bar-wrapper">
|
||||
@@ -38,16 +36,34 @@
|
||||
</div>
|
||||
|
||||
<div class="action-content">
|
||||
<div class="text-block">
|
||||
<span class="name">{{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }}</span>
|
||||
<span class="cnsl-secondary-text description">{{
|
||||
'ONBOARDING.EVENTS.' + action[0] + '.description' | translate
|
||||
}}</span>
|
||||
<div class="action-content-row">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? action[1].darkcolor + 50 : action[1].lightcolor + 50
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="inner"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? action[1].darkcolor : action[1].lightcolor,
|
||||
color: (themeService.isDarkTheme | async) ? action[1].lightcolor : action[1].darkcolor
|
||||
}"
|
||||
>
|
||||
<i class="{{ action[1].iconClasses }}"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-block">
|
||||
<span class="name">{{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }}</span>
|
||||
<span class="cnsl-secondary-text description">{{
|
||||
'ONBOARDING.EVENTS.' + action[0] + '.description' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<div class="action-row">
|
||||
<span>{{ 'ACTIONS.SETUP' | translate }}</span>
|
||||
<span>{{ 'ONBOARDING.EVENTS.' + action[0] + '.action' | translate }}</span>
|
||||
<mat-icon class="icon">keyboard_arrow_right</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -23,14 +23,10 @@
|
||||
flex-direction: column;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.onboarding-progress-bar-wrapper {
|
||||
@@ -71,7 +67,6 @@
|
||||
|
||||
.action-card {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
margin: 1rem;
|
||||
flex-basis: 270px;
|
||||
text-decoration: none;
|
||||
@@ -91,18 +86,46 @@
|
||||
height: 100%;
|
||||
padding-right: 0.5rem;
|
||||
|
||||
.text-block {
|
||||
.action-content-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: map-get($foreground, text);
|
||||
padding-top: 1rem;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
|
||||
.name {
|
||||
margin-bottom: 1rem;
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50vw;
|
||||
flex-shrink: 0;
|
||||
margin-right: 1rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
.inner {
|
||||
border-radius: 50vw;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 14px;
|
||||
.text-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: map-get($foreground, text);
|
||||
padding-top: 1rem;
|
||||
flex: 1;
|
||||
|
||||
.name {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +134,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.done {
|
||||
.action-content {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.state-circle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -126,6 +155,7 @@
|
||||
margin-right: 1rem;
|
||||
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
|
||||
box-shadow: 0 0 3px #0000001a;
|
||||
border: 1px solid rgba(#8795a1, 0.2);
|
||||
|
||||
i {
|
||||
font-size: 1rem;
|
||||
@@ -168,6 +198,12 @@
|
||||
.state-circle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
.action-content {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding';
|
||||
|
||||
@Component({
|
||||
@@ -10,7 +11,7 @@ import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding';
|
||||
export class OnboardingComponent {
|
||||
public actions = this.adminService.progressEvents;
|
||||
|
||||
constructor(public adminService: AdminService) {
|
||||
constructor(public adminService: AdminService, public themeService: ThemeService) {
|
||||
this.adminService.loadEvents.next(ONBOARDING_EVENTS);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild }
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { BehaviorSubject, catchError, debounceTime, finalize, from, map, Observable, of, pipe, tap } from 'rxjs';
|
||||
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
|
||||
import { Org, OrgNameQuery, OrgQuery } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { Org, OrgNameQuery, OrgQuery, OrgState, OrgStateQuery } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { AuthenticationService } from 'src/app/services/authentication.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
|
||||
@@ -47,9 +47,12 @@ export class OrgContextComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
let query;
|
||||
let query = new OrgQuery();
|
||||
const orgStateQuery = new OrgStateQuery();
|
||||
orgStateQuery.setState(OrgState.ORG_STATE_ACTIVE);
|
||||
query.setStateQuery(orgStateQuery);
|
||||
|
||||
if (filter) {
|
||||
query = new OrgQuery();
|
||||
const orgNameQuery = new OrgNameQuery();
|
||||
orgNameQuery.setName(filter);
|
||||
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
|
@@ -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>
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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>
|
||||
|
@@ -4,3 +4,8 @@
|
||||
max-width: 400px;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.attribute-more-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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>
|
||||
|
@@ -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);
|
||||
|
@@ -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" />
|
||||
|
@@ -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
|
||||
|
@@ -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" />
|
||||
|
@@ -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:
|
||||
|
@@ -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" />
|
||||
|
@@ -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:
|
||||
|
@@ -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" />
|
||||
|
@@ -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:
|
||||
|
@@ -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" />
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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>
|
||||
|
@@ -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) => {
|
||||
|
@@ -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" />
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -2,14 +2,7 @@
|
||||
<div class="found-user-row" *ngFor="let user of users; index as i">
|
||||
<div class="circle">
|
||||
<cnsl-avatar
|
||||
*ngIf="
|
||||
user.human &&
|
||||
user.human.profile &&
|
||||
user.human.profile.displayName &&
|
||||
user.human.profile.firstName &&
|
||||
user.human.profile.lastName;
|
||||
else cog
|
||||
"
|
||||
*ngIf="user.human && user.human.profile; else cog"
|
||||
class="avatar"
|
||||
[name]="user.human.profile.displayName"
|
||||
[avatarUrl]="user.human.profile.avatarUrl || ''"
|
||||
@@ -76,14 +69,7 @@
|
||||
<div class="user-option" data-e2e="user-option">
|
||||
<div class="circle">
|
||||
<cnsl-avatar
|
||||
*ngIf="
|
||||
user.human &&
|
||||
user.human.profile &&
|
||||
user.human.profile.displayName &&
|
||||
user.human.profile.firstName &&
|
||||
user.human.profile.lastName;
|
||||
else cog
|
||||
"
|
||||
*ngIf="user.human && user.human.profile; else cog"
|
||||
class="avatar"
|
||||
[name]="user.human.profile.displayName"
|
||||
[avatarUrl]="user.human.profile.avatarUrl || ''"
|
||||
|
@@ -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 : '';
|
||||
|
@@ -28,6 +28,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
text-transform: uppercase;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.shortcut-btn {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -77,7 +77,7 @@
|
||||
[checked]="selection.isSelected(row)"
|
||||
>
|
||||
<cnsl-avatar
|
||||
*ngIf="row && row?.displayName && row.firstName && row.lastName; else cog"
|
||||
*ngIf="row && row.userType === Type.TYPE_HUMAN; else cog"
|
||||
class="avatar"
|
||||
[name]="row.displayName"
|
||||
[avatarUrl]="row.avatarUrl || ''"
|
||||
|
@@ -68,7 +68,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
@Input() public type: Type | undefined = undefined;
|
||||
|
||||
public filterOpen: boolean = false;
|
||||
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
private userService: ManagementService,
|
||||
|
@@ -91,7 +91,7 @@
|
||||
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.TYPE' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let event">
|
||||
<td mat-cell *matCellDef="let event" data-e2e="event-type-cell">
|
||||
<ng-container *ngIf="event | toobject as event">
|
||||
<span *ngIf="event.type?.localized?.localizedMessage">{{ event.type.localized.localizedMessage }}</span>
|
||||
</ng-container>
|
||||
|
@@ -1,4 +1,6 @@
|
||||
<div class="max-width-container">
|
||||
<h1 class="home-title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
|
||||
|
||||
<div class="home-wrapper enlarged-container">
|
||||
<ng-container *ngIf="['iam.read$'] | hasRole | async; else defaultHome">
|
||||
<cnsl-onboarding></cnsl-onboarding>
|
||||
@@ -10,24 +12,66 @@
|
||||
<p class="disclaimer cnsl-secondary-text">{{ 'HOME.DISCLAIMER' | translate }}</p>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<h2 class="desc">{{ 'ONBOARDING.MOREDESCRIPTION' | translate }}</h2>
|
||||
|
||||
<div class="home-grid-container">
|
||||
<a href="https://zitadel.com/docs" target="_blank" rel="noreferrer" class="grid-item blue">
|
||||
<div class="grid-item-avatar blue">
|
||||
<i class="icon las la-file-alt"></i>
|
||||
<a href="https://zitadel.com/docs" target="_blank" rel="noreferrer" class="grid-item">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? bluedark + 50 : bluelight + 50
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="inner"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? bluedark : bluelight,
|
||||
color: (themeService.isDarkTheme | async) ? bluelight : bluedark
|
||||
}"
|
||||
>
|
||||
<i class="las la-file-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span>{{ 'HOME.DOCUMENTATION.TITLE' | translate }}</span>
|
||||
</a>
|
||||
|
||||
<a href="https://zitadel.com/docs/guides/start/quickstart" target="_blank" rel="noreferrer" class="grid-item green">
|
||||
<div class="grid-item-avatar green">
|
||||
<i class="icon las la-play"></i>
|
||||
<a href="https://zitadel.com/docs/guides/start/quickstart" target="_blank" rel="noreferrer" class="grid-item">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? greendark + 50 : greenlight + 50
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="inner"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? greendark : greenlight,
|
||||
color: (themeService.isDarkTheme | async) ? greenlight : greendark
|
||||
}"
|
||||
>
|
||||
<i class="las la-play"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span>{{ 'HOME.GETSTARTED.TITLE' | translate }}</span>
|
||||
</a>
|
||||
|
||||
<a href="https://zitadel.com/docs/examples/introduction" target="_blank" rel="noreferrer" class="grid-item green">
|
||||
<div class="grid-item-avatar green">
|
||||
<i class="icon las la-play"></i>
|
||||
<a href="https://zitadel.com/docs/examples/introduction" target="_blank" rel="noreferrer" class="grid-item">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? cyandark + 50 : cyanlight + 50
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="inner"
|
||||
[ngStyle]="{
|
||||
background: (themeService.isDarkTheme | async) ? cyandark : cyanlight,
|
||||
color: (themeService.isDarkTheme | async) ? cyanlight : cyandark
|
||||
}"
|
||||
>
|
||||
<i class="las la-play"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span>{{ 'HOME.QUICKSTARTS.TITLE' | translate }}</span>
|
||||
</a>
|
||||
|
@@ -18,6 +18,11 @@
|
||||
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
|
||||
$border-selected-color: if($is-dark-theme, #fff, #000);
|
||||
|
||||
.home-title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.home-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -34,6 +39,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.home-grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
@@ -58,24 +69,15 @@
|
||||
font-size: 14px;
|
||||
background-color: $card-background-color;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
box-shadow: 0 0 15px #0000001a;
|
||||
color: inherit;
|
||||
|
||||
&.edit-state {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&.green {
|
||||
background: linear-gradient(40deg, #059669 30%, #047857);
|
||||
}
|
||||
|
||||
&.blue {
|
||||
background: linear-gradient(40deg, #3b82f6 30%, #4f46e5);
|
||||
}
|
||||
|
||||
.grid-item-avatar {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
@@ -118,6 +120,26 @@
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50vw;
|
||||
flex-shrink: 0;
|
||||
margin-right: 1rem;
|
||||
|
||||
.inner {
|
||||
border-radius: 50vw;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
import { COLORS } from 'src/app/utils/color';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-home',
|
||||
@@ -8,9 +10,18 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
styleUrls: ['./home.component.scss'],
|
||||
})
|
||||
export class HomeComponent {
|
||||
public greendark: string = COLORS[6][700];
|
||||
public greenlight = COLORS[6][200];
|
||||
|
||||
public cyandark: string = COLORS[7][700];
|
||||
public cyanlight = COLORS[7][200];
|
||||
|
||||
public bluedark: string = COLORS[9][700];
|
||||
public bluelight = COLORS[9][200];
|
||||
|
||||
public dark: boolean = true;
|
||||
|
||||
constructor(public authService: GrpcAuthService, breadcrumbService: BreadcrumbService) {
|
||||
constructor(public authService: GrpcAuthService, breadcrumbService: BreadcrumbService, public themeService: ThemeService) {
|
||||
const bread: Breadcrumb = {
|
||||
type: BreadcrumbType.ORG,
|
||||
routerLink: ['/org'],
|
||||
|
@@ -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;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<cnsl-create-layout
|
||||
title="{{ 'APP.PAGES.CREATE' | translate }}"
|
||||
class="app-create-wrapper"
|
||||
[createSteps]="
|
||||
appType?.value?.createType === AppCreateType.OIDC
|
||||
? appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE
|
||||
@@ -22,7 +23,7 @@
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-horizontal-stepper
|
||||
class="stepper"
|
||||
class="stepper {{ 'last-edited-step-' + stepper.selectedIndex }}"
|
||||
*ngIf="!devmode"
|
||||
linear
|
||||
#stepper
|
||||
@@ -334,6 +335,11 @@
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Icon override -->
|
||||
<ng-template matStepperIcon="edit">
|
||||
<mat-icon>check</mat-icon>
|
||||
</ng-template>
|
||||
</mat-horizontal-stepper>
|
||||
|
||||
<div *ngIf="devmode" class="dev">
|
||||
|
@@ -50,6 +50,14 @@ p.desc {
|
||||
.step-description {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.mat-step-icon-content {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
|
@@ -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>
|
||||
|
@@ -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({
|
||||
|
@@ -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,
|
||||
);
|
||||
|
@@ -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') {
|
||||
|
@@ -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">
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -11,10 +11,10 @@
|
||||
<i class="las la-camera"></i>
|
||||
</div>
|
||||
<cnsl-avatar
|
||||
*ngIf="user && user.profile?.displayName && user.profile?.firstName && user.profile?.lastName"
|
||||
*ngIf="user && user.profile"
|
||||
class="avatar"
|
||||
[name]="user.profile?.displayName ?? ''"
|
||||
[avatarUrl]="user.profile?.avatarUrl || ''"
|
||||
[name]="user.profile.displayName"
|
||||
[avatarUrl]="user.profile.avatarUrl || ''"
|
||||
[forColor]="preferredLoginName"
|
||||
[size]="80"
|
||||
>
|
||||
|
@@ -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;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user