diff --git a/docs/providers/documentation/grafana-provider.mdx b/docs/providers/documentation/grafana-provider.mdx
index e559d005b..500d6b15c 100644
--- a/docs/providers/documentation/grafana-provider.mdx
+++ b/docs/providers/documentation/grafana-provider.mdx
@@ -4,6 +4,28 @@ description: "Grafana Provider allows either pull/push alerts from Grafana to Ke
---
Grafana currently supports pulling/pushing alerts. We will add querying and notifying soon.
+## Legacy vs Unified Alerting
+
+Keep supports both Grafana's legacy alerting system and the newer Unified Alerting system. Here are the key differences:
+
+### Legacy Alerting
+- Uses notification channels for alert delivery
+- Configured at the dashboard level
+- Uses a different API endpoint (`/api/alerts` and `/api/alert-notifications`)
+- Simpler setup but fewer features
+- Alerts are tightly coupled with dashboard panels
+
+### Unified Alerting (Default from Grafana 9.0)
+- Uses alert rules and contact points
+- Configured centrally in the Alerting section
+- Uses the newer `/api/v1/alerts` endpoint
+- More powerful features including label-based routing
+- Supports multiple data sources in a single alert rule
+
+
+If you're using Grafana 8.x or earlier, or have explicitly enabled legacy alerting in newer versions, make sure to configure Keep accordingly using the legacy alerting configuration.
+
+
## Inputs
Grafana Provider does not currently support the `notify` function.
diff --git a/ee/identitymanager/identity_managers/keycloak/keycloak_authverifier.py b/ee/identitymanager/identity_managers/keycloak/keycloak_authverifier.py
index d38af7f50..3b974d521 100644
--- a/ee/identitymanager/identity_managers/keycloak/keycloak_authverifier.py
+++ b/ee/identitymanager/identity_managers/keycloak/keycloak_authverifier.py
@@ -21,6 +21,9 @@ def __init__(self, scopes: list[str] = []) -> None:
self.keycloak_realm = os.environ.get("KEYCLOAK_REALM")
self.keycloak_client_id = os.environ.get("KEYCLOAK_CLIENT_ID")
self.keycloak_audience = os.environ.get("KEYCLOAK_AUDIENCE")
+ self.keycloak_verify_cert = (
+ os.environ.get("KEYCLOAK_VERIFY_CERT", "true").lower() == "true"
+ )
if (
not self.keycloak_url
or not self.keycloak_realm
@@ -35,12 +38,14 @@ def __init__(self, scopes: list[str] = []) -> None:
realm_name=self.keycloak_realm,
client_id=self.keycloak_client_id,
client_secret_key=os.environ.get("KEYCLOAK_CLIENT_SECRET"),
+ verify=self.keycloak_verify_cert,
)
self.keycloak_openid_connection = KeycloakOpenIDConnection(
server_url=self.keycloak_url,
realm_name=self.keycloak_realm,
client_id=self.keycloak_client_id,
client_secret_key=os.environ.get("KEYCLOAK_CLIENT_SECRET"),
+ verify=self.keycloak_verify_cert,
)
self.keycloak_uma = KeycloakUMA(connection=self.keycloak_openid_connection)
# will be populated in on_start of the identity manager
diff --git a/ee/identitymanager/identity_managers/keycloak/keycloak_identitymanager.py b/ee/identitymanager/identity_managers/keycloak/keycloak_identitymanager.py
index 0fa6c9b14..e3f774762 100644
--- a/ee/identitymanager/identity_managers/keycloak/keycloak_identitymanager.py
+++ b/ee/identitymanager/identity_managers/keycloak/keycloak_identitymanager.py
@@ -45,13 +45,16 @@ class KeycloakIdentityManager(BaseIdentityManager):
def __init__(self, tenant_id, context_manager: ContextManager, **kwargs):
super().__init__(tenant_id, context_manager, **kwargs)
self.server_url = os.environ.get("KEYCLOAK_URL")
+ self.keycloak_verify_cert = (
+ os.environ.get("KEYCLOAK_VERIFY_CERT", "true").lower() == "true"
+ )
try:
self.keycloak_admin = KeycloakAdmin(
server_url=os.environ["KEYCLOAK_URL"] + "/admin",
username=os.environ.get("KEYCLOAK_ADMIN_USER"),
password=os.environ.get("KEYCLOAK_ADMIN_PASSWORD"),
realm_name=os.environ["KEYCLOAK_REALM"],
- verify=True,
+ verify=self.keycloak_verify_cert,
)
self.client_id = self.keycloak_admin.get_client_id(
os.environ["KEYCLOAK_CLIENT_ID"]
@@ -61,6 +64,7 @@ def __init__(self, tenant_id, context_manager: ContextManager, **kwargs):
client_id=os.environ["KEYCLOAK_CLIENT_ID"],
realm_name=os.environ["KEYCLOAK_REALM"],
client_secret_key=os.environ["KEYCLOAK_CLIENT_SECRET"],
+ verify=self.keycloak_verify_cert,
)
self.admin_url = f'{os.environ["KEYCLOAK_URL"]}/admin/realms/{os.environ["KEYCLOAK_REALM"]}/clients/{self.client_id}'
diff --git a/keep-ui/app/(keep)/providers/provider-form.tsx b/keep-ui/app/(keep)/providers/provider-form.tsx
index 5265b5137..6ab1878da 100644
--- a/keep-ui/app/(keep)/providers/provider-form.tsx
+++ b/keep-ui/app/(keep)/providers/provider-form.tsx
@@ -20,6 +20,11 @@ import {
AccordionHeader,
AccordionBody,
Badge,
+ Tab,
+ TabList,
+ TabGroup,
+ TabPanel,
+ TabPanels,
} from "@tremor/react";
import {
ExclamationCircleIcon,
@@ -58,6 +63,7 @@ import {
getRequiredConfigs,
GroupFields,
} from "./form-fields";
+import ProviderLogs from "./provider-logs";
type ProviderFormProps = {
provider: Provider;
@@ -417,18 +423,227 @@ const ProviderForm = ({
?.filter((scope) => scope.mandatory_for_webhook)
.every((scope) => providerValidatedScopes[scope.name] === true);
+ const [activeTab, setActiveTab] = useState(0);
+
+ const renderFormContent = () => (
+ <>
+
+ {provider.oauth2_url && !provider.installed ? (
+ <>
+
+
+ >
+ ) : null}
+ {Object.keys(provider.config).length > 0 && (
+ <>
+
+ >
+ )}
+
+
+ {/* Render required fields */}
+ {Object.entries(requiredConfigs).map(([field, config]) => (
+
+
+
+ ))}
+
+ {/* Render grouped fields */}
+ {Object.entries(groupedConfigs).map(([name, fields]) => (
+
+
+
+ ))}
+
+ {/* Render optional fields in a card */}
+ {Object.keys(optionalConfigs).length > 0 && (
+
+ Provider Optional Settings
+
+
+ {Object.entries(optionalConfigs).map(([field, config]) => (
+
+
+
+ ))}
+
+
+
+ )}
+
+
+ {provider.can_setup_webhook && !installedProvidersMode && (
+
+ )}
+
+
+ {provider.can_setup_webhook && installedProvidersMode && (
+ <>
+
+
+
+
+
+ >
+ )}
+
+ {provider.supports_webhook && (
+
+ )}
+
+
+ >
+ );
+
return (
-
-
+
+
Connect to {provider.display_name}
- {/* Display the Provisioned Badge if the provider is provisioned */}
{provider.provisioned && (
Provisioned
)}
-
+
{installedProvidersMode && provider.last_pull_time && (
Provider last pull time:{" "}
)}
+
{provider.provisioned && (
{provider.provider_description}
)}
+
{Object.keys(provider.config).length > 0 && (
)}
+
{provider.scopes && provider.scopes.length > 0 && (
)}
-
+ {formErrors && (
+
+ {formErrors}
+
+ )}
+
+ {installedProvidersMode ? (
+
+
+ Configuration
+ Logs
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )}
-
+