* Log (failed) attempt to notify a user with viewer role * Remove https:// prefix from BASE_URL docker env var * Fix cloud heartbeat name * Polishing telegram * Update docker-compose.yml * Update plugin README (#48) * Update README and screenshot, remove plop for build info since version is now displayed prominently * Sign build Co-authored-by: Michael Derynck <michael.derynck@grafana.com> * Build actions (#38) * Drone, github action changes * Minor version updates * Update frontend dependencies * Re-enable unit test Co-authored-by: Michael Derynck <michael.derynck@grafana.com> * Revert stylelint version (#52) * Revert stylelint version * Build plugin as well as lint * Build in previous step Co-authored-by: Michael Derynck <michael.derynck@grafana.com> * Update screenshot (#53) Co-authored-by: Michael Derynck <michael.derynck@grafana.com> * oncall images for docs (#55) * Update README.md * Top menu fix * Fix db encoding * Add api key docs * Reverting utf8 fix * bug fixes * fix for link for OSS version * Fixing utf8 and docker compose * 8080 -> 8000 port for consistency * makeReq * Fixing images * Fixing port * Fixing port * Fixing port * Fixing port * Fixing port * Fixing port * Fixing port * Fixing port * Replace symlink with file for CHANGELOG.MD (#68) Co-authored-by: Michael Derynck <michael.derynck@grafana.com> Co-authored-by: Matias Bordese <mbordese@gmail.com> Co-authored-by: Matvey Kukuy <Matvey-Kuk@users.noreply.github.com> Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com> Co-authored-by: Matvey Kukuy <matvey@amixr.io> Co-authored-by: Michael Derynck <michael.derynck@grafana.com> Co-authored-by: Alyssa Wada <101596687+alyssawada@users.noreply.github.com> Co-authored-by: Yulia Shanyrova <yulia.shanyrova@grafana.com>
189 lines
7.8 KiB
Python
189 lines
7.8 KiB
Python
from django.conf import settings
|
|
from django.core.validators import MinLengthValidator
|
|
from django.db import models
|
|
from django.db.models import JSONField
|
|
|
|
from apps.base.utils import LiveSettingValidator
|
|
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
|
|
|
|
|
|
def generate_public_primary_key_for_live_setting():
|
|
prefix = "L"
|
|
new_public_primary_key = generate_public_primary_key(prefix)
|
|
|
|
failure_counter = 0
|
|
while LiveSetting.objects.filter(public_primary_key=new_public_primary_key).exists():
|
|
new_public_primary_key = increase_public_primary_key_length(
|
|
failure_counter=failure_counter, prefix=prefix, model_name="LiveSetting"
|
|
)
|
|
failure_counter += 1
|
|
|
|
return new_public_primary_key
|
|
|
|
|
|
class LiveSetting(models.Model):
|
|
public_primary_key = models.CharField(
|
|
max_length=20,
|
|
validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)],
|
|
unique=True,
|
|
default=generate_public_primary_key_for_live_setting,
|
|
)
|
|
name = models.CharField(max_length=50, unique=True)
|
|
value = JSONField(null=True, default=None)
|
|
error = models.TextField(null=True, default=None)
|
|
|
|
AVAILABLE_NAMES = (
|
|
"TWILIO_ACCOUNT_SID",
|
|
"TWILIO_AUTH_TOKEN",
|
|
"TWILIO_NUMBER",
|
|
"TWILIO_VERIFY_SERVICE_SID",
|
|
"TELEGRAM_TOKEN",
|
|
"TELEGRAM_WEBHOOK_HOST",
|
|
"SLACK_CLIENT_OAUTH_ID",
|
|
"SLACK_CLIENT_OAUTH_SECRET",
|
|
"SLACK_SIGNING_SECRET",
|
|
"SLACK_INSTALL_RETURN_REDIRECT_HOST",
|
|
"SEND_ANONYMOUS_USAGE_STATS",
|
|
"GRAFANA_CLOUD_ONCALL_TOKEN",
|
|
"GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED",
|
|
"GRAFANA_CLOUD_NOTIFICATIONS_ENABLED",
|
|
)
|
|
|
|
DESCRIPTIONS = {
|
|
"SLACK_SIGNING_SECRET": (
|
|
"Check <a href='"
|
|
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
|
"'>instruction</a> for details how to set up Slack. "
|
|
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
|
"after you update them."
|
|
),
|
|
"SLACK_CLIENT_OAUTH_SECRET": (
|
|
"Check <a href='"
|
|
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
|
"'>instruction</a> for details how to set up Slack. "
|
|
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
|
"after you update them."
|
|
),
|
|
"SLACK_CLIENT_OAUTH_ID": (
|
|
"Check <a href='"
|
|
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
|
"'>instruction</a> for details how to set up Slack. "
|
|
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
|
"after you update them."
|
|
),
|
|
"SLACK_INSTALL_RETURN_REDIRECT_HOST": (
|
|
"Check <a href='"
|
|
"https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup"
|
|
"'>instruction</a> for details how to set up Slack. "
|
|
"Slack secrets can't be verified on the backend, please try installing the Slack Bot "
|
|
"after you update them."
|
|
),
|
|
"TWILIO_ACCOUNT_SID": (
|
|
"Twilio username to allow amixr send sms and make phone calls, "
|
|
"<a href='https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them'>"
|
|
"more info</a>."
|
|
),
|
|
"TWILIO_AUTH_TOKEN": (
|
|
"Twilio password to allow amixr send sms and make calls, "
|
|
"<a href='https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them'>"
|
|
"more info</a>."
|
|
),
|
|
"TWILIO_NUMBER": (
|
|
"Number from which you will receive calls and SMS, "
|
|
"<a href='https://www.twilio.com/docs/phone-numbers'>more info</a>."
|
|
),
|
|
"TWILIO_VERIFY_SERVICE_SID": (
|
|
"SID of Twilio service for number verification. "
|
|
"You can create a service in Twilio web interface. "
|
|
"twilio.com -> verify -> create new service."
|
|
),
|
|
"SENDGRID_API_KEY": (
|
|
"Sendgrid api key to send emails, "
|
|
"<a href='https://sendgrid.com/docs/ui/account-and-settings/api-keys/'>more info</a>."
|
|
),
|
|
"SENDGRID_FROM_EMAIL": (
|
|
"Address to send emails, <a href='https://sendgrid.com/docs/ui/sending-email/senders/'>" "more info</a>."
|
|
),
|
|
"SENDGRID_SECRET_KEY": "It is the secret key to secure receiving inbound emails.",
|
|
"SENDGRID_INBOUND_EMAIL_DOMAIN": "Domain to receive emails for inbound emails integration.",
|
|
"TELEGRAM_TOKEN": (
|
|
"Secret token for Telegram bot, you can get one via " "<a href='https://t.me/BotFather'>BotFather</a>."
|
|
),
|
|
"TELEGRAM_WEBHOOK_HOST": (
|
|
"Externally available URL for Telegram to make requests. Please restart OnCall backend after after update."
|
|
),
|
|
"SEND_ANONYMOUS_USAGE_STATS": (
|
|
"Grafana OnCall will send anonymous, but uniquely-identifiable usage analytics to Grafana Labs."
|
|
" These statistics are sent to https://stats.grafana.org/. For more information on what's sent, look at the "
|
|
"<a href='https://github.com/grafana/oncall/blob/dev/engine/apps/oss_installation/usage_stats.py#L29'> source code</a>."
|
|
),
|
|
"GRAFANA_CLOUD_ONCALL_TOKEN": "Secret token for Grafana Cloud OnCall instance.",
|
|
"GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED": "Enable hearbeat integration with Grafana Cloud OnCall.",
|
|
"GRAFANA_CLOUD_NOTIFICATIONS_ENABLED": "Enable SMS/call notifications via Grafana Cloud OnCall",
|
|
}
|
|
|
|
SECRET_SETTING_NAMES = (
|
|
"TWILIO_ACCOUNT_SID",
|
|
"TWILIO_AUTH_TOKEN",
|
|
"TWILIO_VERIFY_SERVICE_SID",
|
|
"SENDGRID_API_KEY",
|
|
"SENDGRID_SECRET_KEY",
|
|
"SLACK_CLIENT_OAUTH_ID",
|
|
"SLACK_CLIENT_OAUTH_SECRET",
|
|
"SLACK_SIGNING_SECRET",
|
|
"TELEGRAM_TOKEN",
|
|
"GRAFANA_CLOUD_ONCALL_TOKEN",
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@property
|
|
def description(self):
|
|
return self.DESCRIPTIONS.get(self.name)
|
|
|
|
@property
|
|
def default_value(self):
|
|
return self._get_setting_from_setting_file(self.name)
|
|
|
|
@property
|
|
def is_secret(self):
|
|
return self.name in self.SECRET_SETTING_NAMES
|
|
|
|
@classmethod
|
|
def get_setting(cls, setting_name):
|
|
if not settings.FEATURE_LIVE_SETTINGS_ENABLED:
|
|
return cls._get_setting_from_setting_file(setting_name)
|
|
|
|
if setting_name not in cls.AVAILABLE_NAMES:
|
|
raise ValueError(
|
|
f"Setting with name '{setting_name}' is not in list of available names {cls.AVAILABLE_NAMES}"
|
|
)
|
|
|
|
live_setting = cls.objects.filter(name=setting_name).first()
|
|
if live_setting is not None:
|
|
return live_setting.value
|
|
else:
|
|
return cls._get_setting_from_setting_file(setting_name)
|
|
|
|
@classmethod
|
|
def populate_settings_if_needed(cls):
|
|
settings_in_db = cls.objects.filter(name__in=cls.AVAILABLE_NAMES).values_list("name", flat=True)
|
|
setting_names_to_populate = set(cls.AVAILABLE_NAMES) - set(settings_in_db)
|
|
|
|
for setting_name in setting_names_to_populate:
|
|
cls.objects.create(name=setting_name, value=cls._get_setting_from_setting_file(setting_name))
|
|
|
|
@staticmethod
|
|
def _get_setting_from_setting_file(setting_name):
|
|
return getattr(settings, setting_name)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if self.name not in self.AVAILABLE_NAMES:
|
|
raise ValueError(
|
|
f"Setting with name '{self.name}' is not in list of available names {self.AVAILABLE_NAMES}"
|
|
)
|
|
|
|
self.error = LiveSettingValidator(live_setting=self).get_error()
|
|
|
|
super().save(*args, **kwargs)
|