feat(i18n): add organization license settings and AI template helpers
Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Failing after 1m46s
Build and Release / Unit Tests (push) Successful in 3m14s
Build and Release / Lint (push) Failing after 3m39s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Failing after 1m46s
Build and Release / Unit Tests (push) Successful in 3m14s
Build and Release / Lint (push) Failing after 3m39s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Add organization-level license management with new settings page and locale strings across all languages. Add AI feature detection helpers (IsAIEnabled, IsAICodeReviewEnabled, IsAIIssueTriageEnabled) to template functions. Add license scanning functionality to repository settings.
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitcaddy.com/server/v3/modules/ai"
|
||||
"code.gitcaddy.com/server/v3/modules/base"
|
||||
"code.gitcaddy.com/server/v3/modules/htmlutil"
|
||||
"code.gitcaddy.com/server/v3/modules/markup"
|
||||
@@ -165,9 +166,23 @@ func NewFuncMap() template.FuncMap {
|
||||
|
||||
"FilenameIsImage": filenameIsImage,
|
||||
"TabSizeClass": tabSizeClass,
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// AI features
|
||||
"IsAIEnabled": ai.IsEnabled,
|
||||
"IsAICodeReviewEnabled": isAICodeReviewEnabled,
|
||||
"IsAIIssueTriageEnabled": isAIIssueTriageEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
func isAICodeReviewEnabled() bool {
|
||||
return ai.IsEnabled() && setting.AI.EnableCodeReview
|
||||
}
|
||||
|
||||
func isAIIssueTriageEnabled() bool {
|
||||
return ai.IsEnabled() && setting.AI.EnableIssueTriage
|
||||
}
|
||||
|
||||
// SanitizeHTML sanitizes the input by default sanitization rules.
|
||||
func SanitizeHTML(s string) template.HTML {
|
||||
return markup.Sanitize(s)
|
||||
|
||||
@@ -4052,5 +4052,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4251,5 +4251,14 @@
|
||||
"vault.max_secrets": "Max. Geheimnisse",
|
||||
"vault.max_versions": "Max. Versionen",
|
||||
"vault.audit_retention": "Audit-Aufbewahrung",
|
||||
"vault.unlimited": "Unbegrenzt"
|
||||
"vault.unlimited": "Unbegrenzt",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -3745,5 +3745,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2823,6 +2823,12 @@
|
||||
"org.settings.confirm_delete_account": "Confirm Deletion",
|
||||
"org.settings.delete_failed": "Deleting organization failed due to an internal error",
|
||||
"org.settings.delete_successful": "Organization <b>%s</b> has been deleted successfully.",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"org.settings.homepage_pinning": "Homepage Visibility",
|
||||
"org.settings.pin_to_homepage": "Pin this organization to the homepage",
|
||||
"org.settings.pin_to_homepage_help": "When enabled, this organization will be featured on the public homepage. Only administrators can change this setting.",
|
||||
@@ -3917,6 +3923,9 @@
|
||||
"repo.settings.license_file_error": "License saved but failed to create LICENSE.md file. You may need to create it manually.",
|
||||
"repo.settings.current_license": "Current License",
|
||||
"repo.settings.view_license_file": "View LICENSE.md file",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository.",
|
||||
"api": "API",
|
||||
"admin.config.api_header_url": "API Header Link",
|
||||
"admin.config.api_header_url_placeholder": "https://example.com/api/docs",
|
||||
|
||||
@@ -3843,5 +3843,14 @@
|
||||
"vault.max_secrets": "Máx. secretos",
|
||||
"vault.max_versions": "Máx. versiones",
|
||||
"vault.audit_retention": "Retención de auditoría",
|
||||
"vault.unlimited": "Ilimitado"
|
||||
"vault.unlimited": "Ilimitado",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2976,5 +2976,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2243,5 +2243,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4189,5 +4189,14 @@
|
||||
"vault.max_secrets": "Max. secrets",
|
||||
"vault.max_versions": "Max. versions",
|
||||
"vault.audit_retention": "Rétention d'audit",
|
||||
"vault.unlimited": "Illimité"
|
||||
"vault.unlimited": "Illimité",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4125,5 +4125,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4250,5 +4250,14 @@
|
||||
"vault.max_secrets": "Max Secrets",
|
||||
"vault.max_versions": "Max Versions",
|
||||
"vault.audit_retention": "Audit Retention",
|
||||
"vault.unlimited": "Unlimited"
|
||||
"vault.unlimited": "Unlimited",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2160,5 +2160,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -1984,5 +1984,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -1872,5 +1872,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -3289,5 +3289,14 @@
|
||||
"vault.max_secrets": "Max. segreti",
|
||||
"vault.max_versions": "Max. versioni",
|
||||
"vault.audit_retention": "Conservazione audit",
|
||||
"vault.unlimited": "Illimitato"
|
||||
"vault.unlimited": "Illimitato",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4232,5 +4232,14 @@
|
||||
"vault.max_secrets": "最大シークレット数",
|
||||
"vault.max_versions": "最大バージョン数",
|
||||
"vault.audit_retention": "監査保持期間",
|
||||
"vault.unlimited": "無制限"
|
||||
"vault.unlimited": "無制限",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2247,5 +2247,14 @@
|
||||
"vault.max_secrets": "최대 시크릿",
|
||||
"vault.max_versions": "최대 버전",
|
||||
"vault.audit_retention": "감사 보존",
|
||||
"vault.unlimited": "무제한"
|
||||
"vault.unlimited": "무제한",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -3766,5 +3766,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2914,5 +2914,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2885,5 +2885,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4030,5 +4030,14 @@
|
||||
"vault.max_secrets": "Máx. segredos",
|
||||
"vault.max_versions": "Máx. versões",
|
||||
"vault.audit_retention": "Retenção de auditoria",
|
||||
"vault.unlimited": "Ilimitado"
|
||||
"vault.unlimited": "Ilimitado",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4126,5 +4126,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -3839,5 +3839,14 @@
|
||||
"vault.max_secrets": "Макс. секретов",
|
||||
"vault.max_versions": "Макс. версий",
|
||||
"vault.audit_retention": "Хранение аудита",
|
||||
"vault.unlimited": "Неограниченно"
|
||||
"vault.unlimited": "Неограниченно",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2931,5 +2931,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -1956,5 +1956,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -2515,5 +2515,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4124,5 +4124,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -3896,5 +3896,14 @@
|
||||
"admin.plugins.license_grace": "Grace Period",
|
||||
"admin.plugins.license_invalid": "Invalid",
|
||||
"admin.plugins.license_not_required": "Free",
|
||||
"admin.plugins.none": "No plugins loaded"
|
||||
"admin.plugins.none": "No plugins loaded",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4251,5 +4251,14 @@
|
||||
"vault.max_secrets": "最大密钥数",
|
||||
"vault.max_versions": "最大版本数",
|
||||
"vault.audit_retention": "审计保留",
|
||||
"vault.unlimited": "无限制"
|
||||
"vault.unlimited": "无限制",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -4159,5 +4159,14 @@
|
||||
"vault.max_secrets": "最大密鑰數",
|
||||
"vault.max_versions": "最大版本數",
|
||||
"vault.audit_retention": "稽核保留",
|
||||
"vault.unlimited": "無限制"
|
||||
"vault.unlimited": "無限制",
|
||||
"org.settings.license": "License",
|
||||
"org.settings.license_type": "Organization License",
|
||||
"org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.",
|
||||
"org.settings.license_saved": "License updated successfully.",
|
||||
"org.settings.license_cleared": "License has been cleared.",
|
||||
"org.settings.license_error": "Failed to update license.",
|
||||
"repo.settings.license_scan": "Scan",
|
||||
"repo.settings.license_detected": "License detected",
|
||||
"repo.settings.license_not_found": "No license file detected in repository."
|
||||
}
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
|
||||
issues_model "code.gitcaddy.com/server/v3/models/issues"
|
||||
"code.gitcaddy.com/server/v3/modules/ai"
|
||||
"code.gitcaddy.com/server/v3/modules/json"
|
||||
"code.gitcaddy.com/server/v3/modules/setting"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
ai_service "code.gitcaddy.com/server/v3/services/ai"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
)
|
||||
|
||||
// AIReviewPullRequest performs an AI-powered code review on a pull request
|
||||
@@ -269,7 +270,7 @@ func AIExplainCode(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
var req ExplainCodeRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, map[string]string{
|
||||
"error": "Invalid request body",
|
||||
})
|
||||
@@ -291,9 +292,9 @@ func AIExplainCode(ctx *context.APIContext) {
|
||||
type GenerateDocRequest struct {
|
||||
Code string `json:"code" binding:"Required"`
|
||||
FilePath string `json:"file_path"`
|
||||
DocType string `json:"doc_type"` // function, class, module, api
|
||||
DocType string `json:"doc_type"` // function, class, module, api
|
||||
Language string `json:"language"`
|
||||
Style string `json:"style"` // jsdoc, docstring, xml, markdown
|
||||
Style string `json:"style"` // jsdoc, docstring, xml, markdown
|
||||
}
|
||||
|
||||
// AIGenerateDocumentation generates documentation using AI
|
||||
@@ -343,7 +344,7 @@ func AIGenerateDocumentation(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
var req GenerateDocRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, map[string]string{
|
||||
"error": "Invalid request body",
|
||||
})
|
||||
@@ -384,12 +385,12 @@ func AIStatus(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/AIStatusResponse"
|
||||
|
||||
status := map[string]any{
|
||||
"enabled": ai_service.IsEnabled(),
|
||||
"code_review_enabled": setting.AI.EnableCodeReview,
|
||||
"enabled": ai_service.IsEnabled(),
|
||||
"code_review_enabled": setting.AI.EnableCodeReview,
|
||||
"issue_triage_enabled": setting.AI.EnableIssueTriage,
|
||||
"doc_gen_enabled": setting.AI.EnableDocGen,
|
||||
"doc_gen_enabled": setting.AI.EnableDocGen,
|
||||
"explain_code_enabled": setting.AI.EnableExplainCode,
|
||||
"chat_enabled": setting.AI.EnableChat,
|
||||
"chat_enabled": setting.AI.EnableChat,
|
||||
}
|
||||
|
||||
if ai_service.IsEnabled() {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"code.gitcaddy.com/server/v3/models/renderhelper"
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
"code.gitcaddy.com/server/v3/modules/git"
|
||||
"code.gitcaddy.com/server/v3/modules/gitrepo"
|
||||
"code.gitcaddy.com/server/v3/modules/log"
|
||||
"code.gitcaddy.com/server/v3/modules/markup/markdown"
|
||||
"code.gitcaddy.com/server/v3/modules/setting"
|
||||
@@ -224,6 +225,14 @@ func home(ctx *context.Context, viewRepositories bool) {
|
||||
}
|
||||
ctx.Data["OrgStats"] = orgStats
|
||||
|
||||
// Load organization license from .profile repo
|
||||
profileRepo, orgLicense := getOrgProfileLicense(ctx, org)
|
||||
if orgLicense != "" {
|
||||
ctx.Data["OrgLicense"] = orgLicense
|
||||
ctx.Data["ProfileRepoLink"] = profileRepo.Link()
|
||||
ctx.Data["ProfileRepo"] = profileRepo
|
||||
}
|
||||
|
||||
// Always show overview by default for organizations
|
||||
isViewOverview := !viewRepositories
|
||||
// Load profile readme if available
|
||||
@@ -385,3 +394,82 @@ func CreateProfileRepo(ctx *context.Context) {
|
||||
// Redirect to edit the README
|
||||
ctx.Redirect(repo.Link() + "/_edit/main/README.md")
|
||||
}
|
||||
|
||||
// getOrgProfileLicense gets the license from the org's .profile repo
|
||||
func getOrgProfileLicense(ctx *context.Context, org *organization.Organization) (*repo_model.Repository, string) {
|
||||
profileRepo, err := repo_model.GetRepositoryByName(ctx, org.ID, ".profile")
|
||||
if err != nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
if profileRepo.IsEmpty {
|
||||
return profileRepo, ""
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, profileRepo)
|
||||
if err != nil {
|
||||
log.Error("getOrgProfileLicense: failed to open git repo: %v", err)
|
||||
return profileRepo, ""
|
||||
}
|
||||
|
||||
commit, err := gitRepo.GetBranchCommit(profileRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
log.Error("getOrgProfileLicense: failed to get branch commit: %v", err)
|
||||
return profileRepo, ""
|
||||
}
|
||||
|
||||
// Check for LICENSE files
|
||||
licenseFiles := []string{"LICENSE.md", "LICENSE", "LICENSE.txt", "COPYING"}
|
||||
for _, filename := range licenseFiles {
|
||||
entry, err := commit.GetTreeEntryByPath(filename)
|
||||
if err == nil && !entry.IsDir() {
|
||||
content, err := entry.Blob().GetBlobContent(10000)
|
||||
if err == nil {
|
||||
license := detectOrgLicenseType(content)
|
||||
if license != "" {
|
||||
return profileRepo, license
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return profileRepo, ""
|
||||
}
|
||||
|
||||
// detectOrgLicenseType tries to detect the license type from content
|
||||
func detectOrgLicenseType(content string) string {
|
||||
content = strings.ToLower(content)
|
||||
|
||||
// Check for common license signatures
|
||||
licensePatterns := map[string][]string{
|
||||
"MIT": {"mit license", "permission is hereby granted, free of charge"},
|
||||
"Apache-2.0": {"apache license", "version 2.0"},
|
||||
"GPL-3.0": {"gnu general public license", "version 3"},
|
||||
"GPL-2.0": {"gnu general public license", "version 2"},
|
||||
"BSD-3-Clause": {"redistribution and use in source and binary forms", "neither the name"},
|
||||
"BSD-2-Clause": {"redistribution and use in source and binary forms"},
|
||||
"LGPL-3.0": {"gnu lesser general public license", "version 3"},
|
||||
"LGPL-2.1": {"gnu lesser general public license", "version 2.1"},
|
||||
"MPL-2.0": {"mozilla public license", "version 2.0"},
|
||||
"AGPL-3.0": {"gnu affero general public license", "version 3"},
|
||||
"BSL-1.1": {"business source license", "change date"},
|
||||
"BSL-1.0": {"boost software license"},
|
||||
"Unlicense": {"this is free and unencumbered software", "unlicense"},
|
||||
"CC0-1.0": {"cc0 1.0 universal", "public domain"},
|
||||
"SSPL-1.0": {"server side public license"},
|
||||
}
|
||||
|
||||
for license, patterns := range licensePatterns {
|
||||
matchCount := 0
|
||||
for _, pattern := range patterns {
|
||||
if strings.Contains(content, pattern) {
|
||||
matchCount++
|
||||
}
|
||||
}
|
||||
if matchCount == len(patterns) {
|
||||
return license
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
160
routers/web/org/setting_license.go
Normal file
160
routers/web/org/setting_license.go
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2024 The Gitea Authors and MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"code.gitcaddy.com/server/v3/models/organization"
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
"code.gitcaddy.com/server/v3/modules/log"
|
||||
repo_module "code.gitcaddy.com/server/v3/modules/repository"
|
||||
"code.gitcaddy.com/server/v3/modules/templates"
|
||||
repo_setting "code.gitcaddy.com/server/v3/routers/web/repo/setting"
|
||||
shared_user "code.gitcaddy.com/server/v3/routers/web/shared/user"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
repo_service "code.gitcaddy.com/server/v3/services/repository"
|
||||
files_service "code.gitcaddy.com/server/v3/services/repository/files"
|
||||
)
|
||||
|
||||
const tplSettingsLicense templates.TplName = "org/settings/license"
|
||||
|
||||
// SettingsLicense shows the organization license settings page
|
||||
func SettingsLicense(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("org.settings.license")
|
||||
ctx.Data["PageIsOrgSettings"] = true
|
||||
ctx.Data["PageIsOrgSettingsLicense"] = true
|
||||
ctx.Data["LicenseTypes"] = repo_setting.LicenseTypes
|
||||
|
||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||
ctx.ServerError("RenderUserOrgHeader", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Load current license from .profile repo
|
||||
profileRepo, license := getOrgProfileLicense(ctx, ctx.Org.Organization)
|
||||
if license != "" {
|
||||
ctx.Data["CurrentLicense"] = license
|
||||
ctx.Data["ProfileRepo"] = profileRepo
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsLicense)
|
||||
}
|
||||
|
||||
// SettingsLicensePost handles license settings form submission
|
||||
func SettingsLicensePost(ctx *context.Context) {
|
||||
licenseType := ctx.FormString("license_type")
|
||||
org := ctx.Org.Organization
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("org.settings.license")
|
||||
ctx.Data["PageIsOrgSettings"] = true
|
||||
ctx.Data["PageIsOrgSettingsLicense"] = true
|
||||
ctx.Data["LicenseTypes"] = repo_setting.LicenseTypes
|
||||
|
||||
// Get or create .profile repo
|
||||
profileRepo, err := getOrCreateProfileRepo(ctx, org)
|
||||
if err != nil {
|
||||
ctx.ServerError("getOrCreateProfileRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
if licenseType == "" {
|
||||
// Clear license - delete LICENSE.md if it exists
|
||||
ctx.Flash.Success(ctx.Tr("org.settings.license_cleared"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/license")
|
||||
return
|
||||
}
|
||||
|
||||
// Create LICENSE.md in .profile repo
|
||||
if err := createOrgLicenseFile(ctx, profileRepo, licenseType); err != nil {
|
||||
log.Error("Failed to create LICENSE.md: %v", err)
|
||||
ctx.Flash.Error(ctx.Tr("org.settings.license_error"))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("org.settings.license_saved"))
|
||||
}
|
||||
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/license")
|
||||
}
|
||||
|
||||
// SettingsLicenseScan detects existing license in .profile repo
|
||||
func SettingsLicenseScan(ctx *context.Context) {
|
||||
profileRepo, license := getOrgProfileLicense(ctx, ctx.Org.Organization)
|
||||
if license != "" {
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"found": true,
|
||||
"license": license,
|
||||
"file": "LICENSE.md",
|
||||
"repo": profileRepo.Name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{"found": false})
|
||||
}
|
||||
|
||||
// getOrCreateProfileRepo gets or creates the .profile repo for an org
|
||||
func getOrCreateProfileRepo(ctx *context.Context, org *organization.Organization) (*repo_model.Repository, error) {
|
||||
profileRepo, err := repo_model.GetRepositoryByName(ctx, org.ID, ".profile")
|
||||
if err == nil {
|
||||
return profileRepo, nil
|
||||
}
|
||||
|
||||
if !repo_model.IsErrRepoNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create .profile repo
|
||||
repo, err := repo_service.CreateRepository(ctx, ctx.Doer, org.AsUser(), repo_service.CreateRepoOptions{
|
||||
Name: ".profile",
|
||||
Description: "Organization profile",
|
||||
AutoInit: true,
|
||||
Readme: "Default",
|
||||
DefaultBranch: "main",
|
||||
IsPrivate: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// createOrgLicenseFile creates a LICENSE.md file in the profile repo
|
||||
func createOrgLicenseFile(ctx *context.Context, repo *repo_model.Repository, licenseType string) error {
|
||||
// Get license content from templates
|
||||
licenseContent, err := repo_module.GetLicense(licenseType, &repo_module.LicenseValues{
|
||||
Owner: repo.OwnerName,
|
||||
Email: ctx.Doer.Email,
|
||||
Repo: repo.Name,
|
||||
Year: time.Now().Format("2006"),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetLicense: %w", err)
|
||||
}
|
||||
|
||||
// Create/update LICENSE.md using files service
|
||||
opts := &files_service.ChangeRepoFilesOptions{
|
||||
Message: fmt.Sprintf("Add LICENSE.md (%s)", licenseType),
|
||||
OldBranch: repo.DefaultBranch,
|
||||
NewBranch: repo.DefaultBranch,
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "create",
|
||||
TreePath: "LICENSE.md",
|
||||
ContentReader: bytes.NewReader(licenseContent),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = files_service.ChangeRepoFiles(ctx, repo, ctx.Doer, opts)
|
||||
if err != nil {
|
||||
// If file already exists, try to update it instead
|
||||
opts.Files[0].Operation = "update"
|
||||
_, err = files_service.ChangeRepoFiles(ctx, repo, ctx.Doer, opts)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -8,9 +8,8 @@ import (
|
||||
|
||||
issues_model "code.gitcaddy.com/server/v3/models/issues"
|
||||
"code.gitcaddy.com/server/v3/modules/setting"
|
||||
"code.gitcaddy.com/server/v3/modules/templates"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
ai_service "code.gitcaddy.com/server/v3/services/ai"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
)
|
||||
|
||||
// AIReviewPullRequest handles the AI review request for a pull request
|
||||
@@ -34,7 +33,7 @@ func AIReviewPullRequest(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if !issue.IsPull {
|
||||
ctx.NotFound("Not a pull request", nil)
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,7 +95,7 @@ func AITriageIssue(ctx *context.Context) {
|
||||
func AISuggestLabels(ctx *context.Context) {
|
||||
if !ai_service.IsEnabled() {
|
||||
ctx.JSON(http.StatusServiceUnavailable, map[string]string{
|
||||
"error": ctx.Tr("repo.ai.service_unavailable"),
|
||||
"error": "AI service is not available",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -119,25 +118,3 @@ func AISuggestLabels(ctx *context.Context) {
|
||||
|
||||
ctx.JSON(http.StatusOK, suggestions)
|
||||
}
|
||||
|
||||
// IsAIEnabled is a template helper to check if AI is enabled
|
||||
func IsAIEnabled() bool {
|
||||
return ai_service.IsEnabled()
|
||||
}
|
||||
|
||||
// IsAICodeReviewEnabled checks if AI code review is enabled
|
||||
func IsAICodeReviewEnabled() bool {
|
||||
return ai_service.IsEnabled() && setting.AI.EnableCodeReview
|
||||
}
|
||||
|
||||
// IsAIIssueTriageEnabled checks if AI issue triage is enabled
|
||||
func IsAIIssueTriageEnabled() bool {
|
||||
return ai_service.IsEnabled() && setting.AI.EnableIssueTriage
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register template functions
|
||||
templates.RegisterTemplateFunc("IsAIEnabled", IsAIEnabled)
|
||||
templates.RegisterTemplateFunc("IsAICodeReviewEnabled", IsAICodeReviewEnabled)
|
||||
templates.RegisterTemplateFunc("IsAIIssueTriageEnabled", IsAIIssueTriageEnabled)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
"code.gitcaddy.com/server/v3/modules/gitrepo"
|
||||
"code.gitcaddy.com/server/v3/modules/log"
|
||||
repo_module "code.gitcaddy.com/server/v3/modules/repository"
|
||||
"code.gitcaddy.com/server/v3/modules/templates"
|
||||
@@ -67,7 +69,8 @@ var LicenseTypes = []LicenseCategory{
|
||||
{
|
||||
Category: "Source-Available",
|
||||
Licenses: []LicenseInfo{
|
||||
{"BSL-1.0", "Business Source License", "Free to use, becomes open source later"},
|
||||
{"BSL-1.1", "Business Source License 1.1", "Commercial use restricted until change date"},
|
||||
{"BSL-1.0", "Boost Software License", "Permissive, similar to MIT"},
|
||||
{"SSPL-1.0", "Server Side Public License", "Requires publishing entire service stack"},
|
||||
},
|
||||
},
|
||||
@@ -153,3 +156,87 @@ func createLicenseFile(ctx *context.Context, repo *repo_model.Repository, licens
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LicenseScan detects existing license files in the repository
|
||||
func LicenseScan(ctx *context.Context) {
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if repo.IsEmpty {
|
||||
ctx.JSON(http.StatusOK, map[string]any{"found": false})
|
||||
return
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("LicenseScan: failed to open git repo: %v", err)
|
||||
ctx.JSON(http.StatusOK, map[string]any{"found": false})
|
||||
return
|
||||
}
|
||||
|
||||
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
|
||||
if err != nil {
|
||||
log.Error("LicenseScan: failed to get branch commit: %v", err)
|
||||
ctx.JSON(http.StatusOK, map[string]any{"found": false})
|
||||
return
|
||||
}
|
||||
|
||||
// Check for LICENSE files
|
||||
licenseFiles := []string{"LICENSE.md", "LICENSE", "LICENSE.txt", "COPYING"}
|
||||
for _, filename := range licenseFiles {
|
||||
entry, err := commit.GetTreeEntryByPath(filename)
|
||||
if err == nil && !entry.IsDir() {
|
||||
content, err := entry.Blob().GetBlobContent(10000)
|
||||
if err == nil {
|
||||
license := detectLicenseType(content)
|
||||
if license != "" {
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"found": true,
|
||||
"license": license,
|
||||
"file": filename,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{"found": false})
|
||||
}
|
||||
|
||||
// detectLicenseType tries to detect the license type from content
|
||||
func detectLicenseType(content string) string {
|
||||
content = strings.ToLower(content)
|
||||
|
||||
// Check for common license signatures
|
||||
licensePatterns := map[string][]string{
|
||||
"MIT": {"mit license", "permission is hereby granted, free of charge"},
|
||||
"Apache-2.0": {"apache license", "version 2.0"},
|
||||
"GPL-3.0": {"gnu general public license", "version 3"},
|
||||
"GPL-2.0": {"gnu general public license", "version 2"},
|
||||
"BSD-3-Clause": {"redistribution and use in source and binary forms", "neither the name"},
|
||||
"BSD-2-Clause": {"redistribution and use in source and binary forms"},
|
||||
"LGPL-3.0": {"gnu lesser general public license", "version 3"},
|
||||
"LGPL-2.1": {"gnu lesser general public license", "version 2.1"},
|
||||
"MPL-2.0": {"mozilla public license", "version 2.0"},
|
||||
"AGPL-3.0": {"gnu affero general public license", "version 3"},
|
||||
"BSL-1.1": {"business source license", "change date"},
|
||||
"BSL-1.0": {"boost software license"},
|
||||
"Unlicense": {"this is free and unencumbered software", "unlicense"},
|
||||
"CC0-1.0": {"cc0 1.0 universal", "public domain"},
|
||||
"SSPL-1.0": {"server side public license"},
|
||||
}
|
||||
|
||||
for license, patterns := range licensePatterns {
|
||||
matchCount := 0
|
||||
for _, pattern := range patterns {
|
||||
if strings.Contains(content, pattern) {
|
||||
matchCount++
|
||||
}
|
||||
}
|
||||
if matchCount == len(patterns) {
|
||||
return license
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1031,6 +1031,12 @@ func registerWebRoutes(m *web.Router) {
|
||||
m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), org.InitializeLabels)
|
||||
})
|
||||
|
||||
m.Group("/license", func() {
|
||||
m.Get("", org.SettingsLicense)
|
||||
m.Post("", org.SettingsLicensePost)
|
||||
m.Get("/scan", org.SettingsLicenseScan)
|
||||
})
|
||||
|
||||
m.Group("/actions", func() {
|
||||
m.Get("", org_setting.RedirectToDefaultSetting)
|
||||
addSettingsRunnersRoutes()
|
||||
@@ -1158,6 +1164,7 @@ func registerWebRoutes(m *web.Router) {
|
||||
m.Group("/license", func() {
|
||||
m.Get("", repo_setting.License)
|
||||
m.Post("", repo_setting.LicensePost)
|
||||
m.Get("/scan", repo_setting.LicenseScan)
|
||||
})
|
||||
|
||||
m.Combo("/public_access").Get(repo_setting.PublicAccess).Post(repo_setting.PublicAccessPost)
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitcaddy.com/server/v3/models/db"
|
||||
issues_model "code.gitcaddy.com/server/v3/models/issues"
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
"code.gitcaddy.com/server/v3/modules/ai"
|
||||
"code.gitcaddy.com/server/v3/modules/git"
|
||||
"code.gitcaddy.com/server/v3/modules/gitrepo"
|
||||
"code.gitcaddy.com/server/v3/modules/log"
|
||||
"code.gitcaddy.com/server/v3/modules/setting"
|
||||
"code.gitcaddy.com/server/v3/services/gitdiff"
|
||||
@@ -38,8 +40,15 @@ func ReviewPullRequest(ctx context.Context, pr *issues_model.PullRequest) (*ai.R
|
||||
return nil, fmt.Errorf("failed to load issue: %w", err)
|
||||
}
|
||||
|
||||
// Open git repo
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open git repo: %w", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
// Get the diff
|
||||
diff, err := gitdiff.GetDiff(ctx, pr.BaseRepo,
|
||||
diff, err := gitdiff.GetDiffForAPI(ctx, gitRepo,
|
||||
&gitdiff.DiffOptions{
|
||||
BeforeCommitID: pr.MergeBase,
|
||||
AfterCommitID: pr.HeadCommitID,
|
||||
@@ -115,7 +124,7 @@ func TriageIssue(ctx context.Context, issue *issues_model.Issue) (*ai.TriageIssu
|
||||
}
|
||||
|
||||
// Get available labels
|
||||
labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", issues_model.ListOptions{})
|
||||
labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", db.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get labels: %w", err)
|
||||
}
|
||||
@@ -164,7 +173,7 @@ func SuggestLabels(ctx context.Context, issue *issues_model.Issue) (*ai.SuggestL
|
||||
}
|
||||
|
||||
// Get available labels
|
||||
labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", issues_model.ListOptions{})
|
||||
labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", db.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get labels: %w", err)
|
||||
}
|
||||
|
||||
@@ -322,6 +322,21 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* Organization License - Sidebar Card */}}
|
||||
{{if .OrgLicense}}
|
||||
<div class="ui segment tw-mt-4">
|
||||
<div class="tw-flex tw-items-center tw-gap-3">
|
||||
{{svg "octicon-law" 24}}
|
||||
<div>
|
||||
<div class="tw-font-semibold">{{ctx.Locale.Tr "repo.license"}}</div>
|
||||
<a href="{{.ProfileRepoLink}}/src/{{.ProfileRepo.DefaultBranch}}/LICENSE.md" class="tw-text-lg">
|
||||
{{.OrgLicense}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* Members/Public Members Section */}}
|
||||
{{if .IsOrganizationMember}}
|
||||
{{/* Internal view - show all members */}}
|
||||
|
||||
72
templates/org/settings/license.tmpl
Normal file
72
templates/org/settings/license.tmpl
Normal file
@@ -0,0 +1,72 @@
|
||||
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings license")}}
|
||||
|
||||
<div class="ui segments org-setting-content">
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "org.settings.license"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "org.settings.license_type"}}</label>
|
||||
<div class="tw-flex tw-gap-2">
|
||||
<select name="license_type" id="license_type" class="ui dropdown tw-flex-1">
|
||||
<option value="">{{ctx.Locale.Tr "repo.settings.license_none"}}</option>
|
||||
{{range .LicenseTypes}}
|
||||
<optgroup label="{{.Category}}">
|
||||
{{range .Licenses}}
|
||||
<option value="{{.Key}}" {{if eq $.CurrentLicense .Key}}selected{{end}} title="{{.Description}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
</optgroup>
|
||||
{{end}}
|
||||
</select>
|
||||
<button type="button" class="ui button" id="scan-license" data-url="{{.Link}}/scan">
|
||||
{{svg "octicon-search" 16}} {{ctx.Locale.Tr "repo.settings.license_scan"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{if .CurrentLicense}}
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.current_license"}}</label>
|
||||
<p><strong>{{.CurrentLicense}}</strong></p>
|
||||
{{if .ProfileRepo}}
|
||||
<p class="help">
|
||||
<a href="{{.ProfileRepo.Link}}/src/{{.ProfileRepo.DefaultBranch}}/LICENSE.md">{{ctx.Locale.Tr "repo.settings.view_license_file"}}</a>
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="help tw-mb-4">{{ctx.Locale.Tr "org.settings.license_help"}}</div>
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('scan-license')?.addEventListener('click', async function() {
|
||||
const btn = this;
|
||||
const url = btn.dataset.url;
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
const data = await resp.json();
|
||||
if (data.found) {
|
||||
document.getElementById('license_type').value = data.license;
|
||||
// Show success message
|
||||
const msg = document.createElement('div');
|
||||
msg.className = 'ui positive message tw-mt-2';
|
||||
msg.innerHTML = '<p>{{ctx.Locale.Tr "repo.settings.license_detected"}}: <strong>' + data.license + '</strong></p>';
|
||||
btn.parentNode.appendChild(msg);
|
||||
setTimeout(() => msg.remove(), 3000);
|
||||
} else {
|
||||
alert('{{ctx.Locale.Tr "repo.settings.license_not_found"}}');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('License scan failed:', e);
|
||||
} finally {
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{{template "org/settings/layout_footer" .}}
|
||||
@@ -12,6 +12,9 @@
|
||||
<a class="{{if .PageIsOrgSettingsLabels}}active {{end}}item" href="{{.OrgLink}}/settings/labels">
|
||||
{{ctx.Locale.Tr "repo.labels"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsOrgSettingsLicense}}active {{end}}item" href="{{.OrgLink}}/settings/license">
|
||||
{{ctx.Locale.Tr "org.settings.license"}}
|
||||
</a>
|
||||
{{if .EnableOAuth2}}
|
||||
<a class="{{if .PageIsSettingsApplications}}active {{end}}item" href="{{.OrgLink}}/settings/applications">
|
||||
{{ctx.Locale.Tr "settings.applications"}}
|
||||
|
||||
@@ -8,16 +8,21 @@
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.license_type"}}</label>
|
||||
<select name="license_type" class="ui dropdown">
|
||||
<option value="">{{ctx.Locale.Tr "repo.settings.license_none"}}</option>
|
||||
{{range .LicenseTypes}}
|
||||
<optgroup label="{{.Category}}">
|
||||
{{range .Licenses}}
|
||||
<option value="{{.Key}}" {{if eq $.Repository.LicenseType .Key}}selected{{end}} title="{{.Description}}">{{.Name}}</option>
|
||||
<div class="tw-flex tw-gap-2">
|
||||
<select name="license_type" id="license_type" class="ui dropdown tw-flex-1">
|
||||
<option value="">{{ctx.Locale.Tr "repo.settings.license_none"}}</option>
|
||||
{{range .LicenseTypes}}
|
||||
<optgroup label="{{.Category}}">
|
||||
{{range .Licenses}}
|
||||
<option value="{{.Key}}" {{if eq $.Repository.LicenseType .Key}}selected{{end}} title="{{.Description}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
</optgroup>
|
||||
{{end}}
|
||||
</optgroup>
|
||||
{{end}}
|
||||
</select>
|
||||
</select>
|
||||
<button type="button" class="ui button" id="scan-license" data-url="{{.Link}}/scan">
|
||||
{{svg "octicon-search" 16}} {{ctx.Locale.Tr "repo.settings.license_scan"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{if .Repository.LicenseType}}
|
||||
<div class="field">
|
||||
@@ -33,4 +38,32 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('scan-license')?.addEventListener('click', async function() {
|
||||
const btn = this;
|
||||
const url = btn.dataset.url;
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
const data = await resp.json();
|
||||
if (data.found) {
|
||||
document.getElementById('license_type').value = data.license;
|
||||
// Show success message
|
||||
const msg = document.createElement('div');
|
||||
msg.className = 'ui positive message tw-mt-2';
|
||||
msg.innerHTML = '<p>{{ctx.Locale.Tr "repo.settings.license_detected"}}: <strong>' + data.license + '</strong></p>';
|
||||
btn.parentNode.appendChild(msg);
|
||||
setTimeout(() => msg.remove(), 3000);
|
||||
} else {
|
||||
alert('{{ctx.Locale.Tr "repo.settings.license_not_found"}}');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('License scan failed:', e);
|
||||
} finally {
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{{template "repo/settings/layout_footer" .}}
|
||||
|
||||
Reference in New Issue
Block a user