Skip to content

Commit f9ac621

Browse files
feat(server, ui): support custom logo (#4334)
* feat(server): support white label * update: ui * refetch branding logo * update * update * update: query * revert caddy change * [autofix.ci] apply automated fixes * lint * [autofix.ci] apply automated fixes * update: remove image button * [autofix.ci] apply automated fixes * update * [autofix.ci] apply automated fixes * update: license key * update * update: remove branding_name * update * update: feature enum * update: lint --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent e86449e commit f9ac621

File tree

24 files changed

+1060
-451
lines changed

24 files changed

+1060
-451
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE server_setting DROP COLUMN branding_logo;
2+
ALTER TABLE server_setting DROP COLUMN branding_icon;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE server_setting ADD COLUMN branding_logo TEXT DEFAULT NULL;
2+
ALTER TABLE server_setting ADD COLUMN branding_icon TEXT DEFAULT NULL;

ee/tabby-db/schema.sqlite

0 Bytes
Binary file not shown.

ee/tabby-db/schema/schema.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ CREATE TABLE server_setting(
6464
network_external_url STRING NOT NULL DEFAULT 'http://localhost:8080'
6565
,
6666
billing_enterprise_license STRING,
67-
security_disable_password_login BOOLEAN NOT NULL DEFAULT FALSE
67+
security_disable_password_login BOOLEAN NOT NULL DEFAULT FALSE,
68+
branding_logo TEXT DEFAULT NULL,
69+
branding_icon TEXT DEFAULT NULL
6870
);
6971
CREATE TABLE email_setting(
7072
id INTEGER PRIMARY KEY AUTOINCREMENT,

ee/tabby-db/schema/schema.svg

Lines changed: 422 additions & 414 deletions
Loading

ee/tabby-db/src/server_setting.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub struct ServerSettingDAO {
1010
pub security_disable_client_side_telemetry: bool,
1111
pub security_disable_password_login: bool,
1212
pub network_external_url: String,
13+
pub branding_logo: Option<String>,
14+
pub branding_icon: Option<String>,
1315
}
1416

1517
const SERVER_SETTING_ROW_ID: i32 = 1;
@@ -35,7 +37,9 @@ impl DbConn {
3537
network_external_url,
3638
security_allowed_register_domain_list,
3739
billing_enterprise_license,
38-
security_disable_password_login
40+
security_disable_password_login,
41+
branding_logo,
42+
branding_icon
3943
FROM server_setting
4044
WHERE id = ?;",
4145
)
@@ -106,6 +110,23 @@ impl DbConn {
106110
Ok(())
107111
}
108112

113+
pub async fn update_branding_setting(
114+
&self,
115+
branding_logo: Option<String>,
116+
branding_icon: Option<String>,
117+
) -> Result<()> {
118+
sqlx::query!(
119+
"UPDATE server_setting SET branding_logo = ?, branding_icon = ? WHERE id = ?",
120+
branding_logo,
121+
branding_icon,
122+
SERVER_SETTING_ROW_ID
123+
)
124+
.execute(&self.pool)
125+
.await?;
126+
127+
Ok(())
128+
}
129+
109130
pub async fn read_enterprise_license(&self) -> Result<Option<String>> {
110131
Ok(sqlx::query_scalar(
111132
"SELECT billing_enterprise_license FROM server_setting WHERE id = ?;",
@@ -142,6 +163,8 @@ mod tests {
142163
security_disable_client_side_telemetry: false,
143164
security_disable_password_login: false,
144165
network_external_url: "http://localhost:8080".into(),
166+
branding_logo: None,
167+
branding_icon: None,
145168
}
146169
}
147170
#[test]

ee/tabby-schema/graphql/schema.graphql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ enum LdapEncryptionKind {
7878
LDAPS
7979
}
8080

81+
enum LicenseFeature {
82+
CUSTOM_LOGO
83+
}
84+
8185
enum LicenseStatus {
8286
OK
8387
EXPIRED
@@ -121,6 +125,11 @@ enum Role {
121125
ASSISTANT
122126
}
123127

128+
input BrandingSettingInput {
129+
brandingLogo: String
130+
brandingIcon: String
131+
}
132+
124133
input CodeQueryInput {
125134
filepath: String
126135
language: String
@@ -468,6 +477,11 @@ type AuthProvider {
468477
kind: AuthProviderKind!
469478
}
470479

480+
type BrandingSetting {
481+
brandingLogo: String
482+
brandingIcon: String
483+
}
484+
471485
type ChatCompletionMessage {
472486
role: String!
473487
content: String!
@@ -692,6 +706,7 @@ type LicenseInfo {
692706
seatsUsed: Int!
693707
issuedAt: DateTime
694708
expiresAt: DateTime
709+
features: [LicenseFeature!]
695710
}
696711

697712
type Message {
@@ -845,6 +860,7 @@ type Mutation {
845860
updateEmailSetting(input: EmailSettingInput!): Boolean!
846861
updateSecuritySetting(input: SecuritySettingInput!): Boolean!
847862
updateNetworkSetting(input: NetworkSettingInput!): Boolean!
863+
updateBrandingSetting(input: BrandingSettingInput!): Boolean!
848864
deleteEmailSetting: Boolean!
849865
uploadLicense(license: String!): Boolean!
850866
resetLicense: Boolean!
@@ -1085,6 +1101,7 @@ type Query {
10851101
emailSetting: EmailSetting
10861102
networkSetting: NetworkSetting!
10871103
securitySetting: SecuritySetting!
1104+
brandingSetting: BrandingSetting!
10881105
gitRepositories(after: String, before: String, first: Int, last: Int): RepositoryConnection!
10891106
"Search files that matches the pattern in the repository."
10901107
repositorySearch(kind: RepositoryKind!, id: ID!, rev: String, pattern: String!): [FileEntrySearchResult!]!

ee/tabby-schema/src/dao.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::{
3030
user_event::{EventKind, UserEvent},
3131
CoreError,
3232
},
33+
setting::BrandingSetting,
3334
thread::{self},
3435
};
3536

@@ -132,6 +133,15 @@ impl From<ServerSettingDAO> for NetworkSetting {
132133
}
133134
}
134135

136+
impl From<ServerSettingDAO> for BrandingSetting {
137+
fn from(value: ServerSettingDAO) -> Self {
138+
Self {
139+
branding_logo: value.branding_logo,
140+
branding_icon: value.branding_icon,
141+
}
142+
}
143+
}
144+
135145
impl TryFrom<IntegrationDAO> for Integration {
136146
type Error = anyhow::Error;
137147
fn try_from(value: IntegrationDAO) -> anyhow::Result<Self> {

ee/tabby-schema/src/schema/license.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ pub enum LicenseStatus {
2323
SeatsExceeded,
2424
}
2525

26+
#[derive(GraphQLEnum, PartialEq, Debug, Clone, Deserialize)]
27+
pub enum LicenseFeature {
28+
CustomLogo,
29+
}
30+
2631
#[derive(GraphQLObject)]
2732
pub struct LicenseInfo {
2833
pub r#type: LicenseType,
@@ -31,6 +36,7 @@ pub struct LicenseInfo {
3136
pub seats_used: i32,
3237
pub issued_at: Option<DateTime<Utc>>,
3338
pub expires_at: Option<DateTime<Utc>>,
39+
pub features: Option<Vec<LicenseFeature>>,
3440
}
3541

3642
impl LicenseInfo {
@@ -89,6 +95,18 @@ impl LicenseInfo {
8995
duration.num_days()
9096
})
9197
}
98+
99+
pub fn ensure_available_features(&self, feature: LicenseFeature) -> Result<()> {
100+
if let Some(features) = &self.features {
101+
if features.contains(&feature) {
102+
return Ok(());
103+
}
104+
}
105+
106+
Err(CoreError::InvalidLicense(
107+
"Your plan doesn't include support for this feature.",
108+
))
109+
}
92110
}
93111

94112
#[async_trait]

ee/tabby-schema/src/schema/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ use self::{
8787
RepositoryKind, RepositoryService, UpdateIntegrationInput,
8888
},
8989
setting::{
90-
NetworkSetting, NetworkSettingInput, SecuritySetting, SecuritySettingInput, SettingService,
90+
BrandingSetting, NetworkSetting, NetworkSettingInput, SecuritySetting,
91+
SecuritySettingInput, SettingService,
9192
},
9293
user_event::{UserEvent, UserEventService},
9394
web_documents::{CreateCustomDocumentInput, CustomWebDocument, WebDocumentService},
@@ -422,6 +423,12 @@ impl Query {
422423
ctx.locator.setting().read_security_setting().await
423424
}
424425

426+
async fn branding_setting(ctx: &Context) -> Result<BrandingSetting> {
427+
let license = ctx.locator.license().read().await?;
428+
license.ensure_available_features(license::LicenseFeature::CustomLogo)?;
429+
ctx.locator.setting().read_branding_setting().await
430+
}
431+
425432
async fn git_repositories(
426433
&self,
427434
ctx: &Context,
@@ -1375,6 +1382,18 @@ impl Mutation {
13751382
Ok(true)
13761383
}
13771384

1385+
async fn update_branding_setting(
1386+
ctx: &Context,
1387+
input: setting::BrandingSettingInput,
1388+
) -> Result<bool> {
1389+
check_admin(ctx).await?;
1390+
let license = ctx.locator.license().read().await?;
1391+
license.ensure_available_features(license::LicenseFeature::CustomLogo)?;
1392+
input.validate()?;
1393+
ctx.locator.setting().update_branding_setting(input).await?;
1394+
Ok(true)
1395+
}
1396+
13781397
async fn delete_email_setting(ctx: &Context) -> Result<bool> {
13791398
check_admin(ctx).await?;
13801399
ctx.locator.email().delete_setting().await?;

0 commit comments

Comments
 (0)