diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index 0dad01620b1d3..27aa2cb4c33eb 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -55,6 +55,7 @@ func TestEntitlements(t *testing.T) { codersdk.FeatureAdvancedTemplateScheduling: 1, codersdk.FeatureWorkspaceProxy: 1, }, + GraceAt: time.Now().Add(59 * 24 * time.Hour), }) res, err := client.Entitlements(context.Background()) require.NoError(t, err) diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index d29dad402e613..fa2f1a9fcfdc0 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -4,6 +4,7 @@ import ( "context" "crypto/ed25519" "fmt" + "math" "time" "github.com/golang-jwt/jwt/v4" @@ -70,6 +71,23 @@ func Entitlements( // LicenseExpires we must be in grace period. entitlement = codersdk.EntitlementGracePeriod } + + // Add warning if license is expiring soon + daysToExpire := int(math.Ceil(claims.LicenseExpires.Sub(now).Hours() / 24)) + isTrial := entitlements.Trial + showWarningDays := 30 + if isTrial { + showWarningDays = 7 + } + isExpiringSoon := daysToExpire > 0 && daysToExpire < showWarningDays + if isExpiringSoon { + day := "day" + if daysToExpire > 1 { + day = "days" + } + entitlements.Warnings = append(entitlements.Warnings, fmt.Sprintf("Your license expires in %d %s.", daysToExpire, day)) + } + for featureName, featureValue := range claims.Features { // Can this be negative? if featureValue <= 0 { diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index 9cd56c67875a3..953a14c1695c1 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -102,6 +102,123 @@ func TestEntitlements(t *testing.T) { fmt.Sprintf("%s is enabled but your license for this feature is expired.", codersdk.FeatureAuditLog.Humanize()), ) }) + t.Run("Expiration warning", func(t *testing.T) { + t.Parallel() + db := dbfake.New() + db.InsertLicense(context.Background(), database.InsertLicenseParams{ + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + codersdk.FeatureAuditLog: 1, + }, + + GraceAt: time.Now().AddDate(0, 0, 2), + ExpiresAt: time.Now().AddDate(0, 0, 5), + }), + Exp: time.Now().AddDate(0, 0, 5), + }) + + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all) + + require.NoError(t, err) + require.True(t, entitlements.HasLicense) + require.False(t, entitlements.Trial) + + require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement) + require.Contains( + t, entitlements.Warnings, + "Your license expires in 2 days.", + ) + }) + + t.Run("Expiration warning for license expiring in 1 day", func(t *testing.T) { + t.Parallel() + db := dbfake.New() + db.InsertLicense(context.Background(), database.InsertLicenseParams{ + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + codersdk.FeatureAuditLog: 1, + }, + + GraceAt: time.Now().AddDate(0, 0, 1), + ExpiresAt: time.Now().AddDate(0, 0, 5), + }), + Exp: time.Now().AddDate(0, 0, 5), + }) + + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all) + + require.NoError(t, err) + require.True(t, entitlements.HasLicense) + require.False(t, entitlements.Trial) + + require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement) + require.Contains( + t, entitlements.Warnings, + "Your license expires in 1 day.", + ) + }) + + t.Run("Expiration warning for trials", func(t *testing.T) { + t.Parallel() + db := dbfake.New() + db.InsertLicense(context.Background(), database.InsertLicenseParams{ + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + codersdk.FeatureAuditLog: 1, + }, + + Trial: true, + GraceAt: time.Now().AddDate(0, 0, 8), + ExpiresAt: time.Now().AddDate(0, 0, 5), + }), + Exp: time.Now().AddDate(0, 0, 5), + }) + + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all) + + require.NoError(t, err) + require.True(t, entitlements.HasLicense) + require.True(t, entitlements.Trial) + + require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement) + require.NotContains( // it should not contain a warning since it is a trial license + t, entitlements.Warnings, + "Your license expires in 8 days.", + ) + }) + + t.Run("Expiration warning for non trials", func(t *testing.T) { + t.Parallel() + db := dbfake.New() + db.InsertLicense(context.Background(), database.InsertLicenseParams{ + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + codersdk.FeatureAuditLog: 1, + }, + + GraceAt: time.Now().AddDate(0, 0, 30), + ExpiresAt: time.Now().AddDate(0, 0, 5), + }), + Exp: time.Now().AddDate(0, 0, 5), + }) + + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all) + + require.NoError(t, err) + require.True(t, entitlements.HasLicense) + require.False(t, entitlements.Trial) + + require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement) + require.NotContains( // it should not contain a warning since it is a trial license + t, entitlements.Warnings, + "Your license expires in 30 days.", + ) + }) + t.Run("SingleLicenseNotEntitled", func(t *testing.T) { t.Parallel() db := dbfake.New() @@ -164,16 +281,18 @@ func TestEntitlements(t *testing.T) { Features: license.Features{ codersdk.FeatureUserLimit: 10, }, + GraceAt: time.Now().Add(59 * 24 * time.Hour), }), - Exp: time.Now().Add(time.Hour), + Exp: time.Now().Add(60 * 24 * time.Hour), }) db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ Features: license.Features{ codersdk.FeatureUserLimit: 1, }, + GraceAt: time.Now().Add(59 * 24 * time.Hour), }), - Exp: time.Now().Add(time.Hour), + Exp: time.Now().Add(60 * 24 * time.Hour), }) entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, empty) require.NoError(t, err) diff --git a/site/src/components/Dashboard/DashboardLayout.tsx b/site/src/components/Dashboard/DashboardLayout.tsx index 843cf77e041f5..55b8d00b55a51 100644 --- a/site/src/components/Dashboard/DashboardLayout.tsx +++ b/site/src/components/Dashboard/DashboardLayout.tsx @@ -25,10 +25,12 @@ export const DashboardLayout: FC = () => { }) const { error: updateCheckError, updateCheck } = updateCheckState.context + const canViewDeployment = Boolean(permissions.viewDeploymentValues) + return ( - + {canViewDeployment && }
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy