diff --git a/.env.example b/.env.example index b8794c10..529d3ce9 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +RUNSERVER_PORT=8080 + SLACK_CLIENT_OAUTH_ID= SLACK_CLIENT_OAUTH_SECRET= SLACK_API_TOKEN= @@ -19,13 +21,13 @@ SENDGRID_FROM_EMAIL= DJANGO_SETTINGS_MODULE=settings.dev SECRET_KEY=jkashdkjashdkjh -BASE_URL=http://localhost:8000 +BASE_URL=http://localhost:8080 FEATURE_TELEGRAM_INTEGRATION_ENABLED=True FEATURE_SLACK_INTEGRATION_ENABLED=True FEATURE_EXTRA_MESSAGING_BACKENDS_ENABLED= -SLACK_INSTALL_RETURN_REDIRECT_HOST=http://localhost:8000 +SLACK_INSTALL_RETURN_REDIRECT_HOST=http://localhost:8080 SOCIAL_AUTH_REDIRECT_IS_HTTPS=False GRAFANA_INCIDENT_STATIC_API_KEY= diff --git a/DEVELOPER.md b/DEVELOPER.md index 8af642f3..37a0a526 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -64,7 +64,7 @@ python manage.py createsuperuser 3. Launch the backend: ```bash # Http server: -python manage.py runserver +python manage.py runserver 8080 # Worker for background tasks (run it in the parallel terminal, don't forget to export .env there) python manage.py start_celery @@ -73,7 +73,7 @@ python manage.py start_celery celery -A engine beat -l info ``` -4. All set! Check out internal API endpoints at http://localhost:8000/. +4. All set! Check out internal API endpoints at http://localhost:8080/. ### Frontend setup @@ -102,7 +102,7 @@ python manage.py issue_invite_for_the_frontend --override 6. Some configuration fields will appear be available. Fill them out and click Initialize OnCall ``` OnCall API URL: -http://host.docker.internal:8000 +http://host.docker.internal:8080 Invitation Token (Single use token to connect Grafana instance): Response from the invite generator command (check above) @@ -226,6 +226,7 @@ pytest --ds=settings.dev - Set Settings to settings/dev.py 5. Create a new Django Server run configuration to Run/Debug the engine - Use a plugin such as EnvFile to load the .env file + - Change port from 8000 to 8080 ## Update drone build The .drone.yml build file must be signed when changes are made to it. Follow these steps: diff --git a/README.md b/README.md index 6e94081a..b9569831 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -Developer-friendly, incident response with brilliant Slack integration. +Developer-friendly incident response with brilliant Slack integration. @@ -20,7 +20,7 @@ curl https://github.com/grafana/oncall/blob/dev/docker-compose.yml -o docker-com 2. Set variables: ```bash -echo "DOMAIN=http://localhost +echo "DOMAIN=http://localhost:8080 SECRET_KEY=my_random_secret_must_be_more_than_32_characters_long RABBITMQ_PASSWORD=rabbitmq_secret_pw MYSQL_PASSWORD=mysql_secret_pw diff --git a/docker-compose-developer.yml b/docker-compose-developer.yml index 622eddd4..e35c3c70 100644 --- a/docker-compose-developer.yml +++ b/docker-compose-developer.yml @@ -7,7 +7,7 @@ services: platform: linux/x86_64 mem_limit: 500m cpus: 0.5 - command: --default-authentication-plugin=mysql_native_password + command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci restart: always ports: - 3306:3306 diff --git a/docker-compose.yml b/docker-compose.yml index 457fd6b5..c4695fd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,13 +92,15 @@ services: depends_on: mysql: condition: service_healthy + rabbitmq: + condition: service_started mysql: image: mysql:5.7 platform: linux/x86_64 mem_limit: 500m cpus: 0.5 - command: --default-authentication-plugin=mysql_native_password + command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci restart: always ports: - 3306:3306 diff --git a/docs/img/GrafanaOnCall_Stack_Fullcolor_black.png b/docs/img/GrafanaOnCall_Stack_Fullcolor_black.png new file mode 100644 index 00000000..94d3c86b Binary files /dev/null and b/docs/img/GrafanaOnCall_Stack_Fullcolor_black.png differ diff --git a/docs/img/How Grafana OnCall works_diagram.png b/docs/img/How Grafana OnCall works_diagram.png new file mode 100644 index 00000000..6812230f Binary files /dev/null and b/docs/img/How Grafana OnCall works_diagram.png differ diff --git a/docs/sources/oncall-api-reference/_index.md b/docs/sources/oncall-api-reference/_index.md index b696b0fc..000ee452 100644 --- a/docs/sources/oncall-api-reference/_index.md +++ b/docs/sources/oncall-api-reference/_index.md @@ -21,15 +21,12 @@ To authorize, use the **Authorization** header: ```shell # With shell, you can just pass the correct header with each request -curl "api_endpoint_here" --header "Authorization: meowmeowmeow" +curl "api_endpoint_here" --header "Authorization: "api_key_here"" ``` -Note that `meowmeowmeow` is a valid key for test purposes. -Replace `meowmeowmeow` with your API key in production. +Grafana OnCall uses API keys to allow access to the API. You can request a new OnCall API key in OnCall -> Settings page. -Grafana OnCall uses API keys to allow access to the API. You can request a new OnCall API key in the API section. - -An API key is specific to a user and a Grafana stack. If you want to switch to a different team configuration, request a different API key. +An API key is specific to a user and a Grafana stack. If you want to switch to a different stack configuration, request a different API key. ## Pagination diff --git a/docs/sources/open-source.md b/docs/sources/open-source.md index 8d77b0d0..fc31bf69 100644 --- a/docs/sources/open-source.md +++ b/docs/sources/open-source.md @@ -17,7 +17,7 @@ We prepared three environments for OSS users: ## Production Environment -TBD +We prepared the helm chart for production environment: https://github.com/grafana/oncall/helm ## Slack Setup @@ -32,7 +32,7 @@ Grafana OnCall Slack integration use a lot of Slack API features: # Choose the unique prefix instead of pretty-turkey-83 # Localtunnel will generate an url, e.g. https://pretty-turkey-83.loca.lt # it is referred as below -lt --port 8000 -s pretty-turkey-83 --print-requests +lt --port 8080 -s pretty-turkey-83 --print-requests ``` 3. If you use localtunnel, open your external URL and click "Continue" to allow requests to bypass the warning page. diff --git a/engine/apps/base/models/live_setting.py b/engine/apps/base/models/live_setting.py index 59126f3d..54a5299d 100644 --- a/engine/apps/base/models/live_setting.py +++ b/engine/apps/base/models/live_setting.py @@ -114,8 +114,8 @@ class LiveSetting(models.Model): ), "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" - " https://github.com/grafana/oncall/blob/dev/engine/apps/oss_installation/usage_stats.py#L29" + " These statistics are sent to https://stats.grafana.org/. For more information on what's sent, look at the " + " source code." ), "GRAFANA_CLOUD_ONCALL_TOKEN": "Secret token for Grafana Cloud OnCall instance.", "GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED": "Enable hearbeat integration with Grafana Cloud OnCall.", diff --git a/engine/settings/base.py b/engine/settings/base.py index 16d605ac..baba3861 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -226,9 +226,7 @@ USE_TZ = True # https://docs.djangoproject.com/en/2.1/howto/static-files/ STATIC_URL = "/static/" -STATICFILES_DIRS = [ - "./static", -] +STATIC_ROOT = "./static/" CELERY_BROKER_URL = "amqp://rabbitmq:rabbitmq@localhost:5672" diff --git a/engine/uwsgi.ini b/engine/uwsgi.ini index 17a92d60..6f612248 100644 --- a/engine/uwsgi.ini +++ b/engine/uwsgi.ini @@ -18,4 +18,4 @@ post-buffering=1 logger=stdio log-format=source=engine:uwsgi status=%(status) method=%(method) path=%(uri) latency=%(secs) google_trace_id=%(var.HTTP_X_CLOUD_TRACE_CONTEXT) protocol=%(proto) resp_size=%(size) req_body_size=%(cl) -log-encoder=format ${strftime:%%Y-%%m-%%d %%H:%%M:%%S} ${msgnl} \ No newline at end of file +log-encoder=format ${strftime:%%Y-%%m-%%d %%H:%%M:%%S} ${msgnl} diff --git a/grafana-plugin/CHANGELOG.md b/grafana-plugin/CHANGELOG.md deleted file mode 120000 index 04c99a55..00000000 --- a/grafana-plugin/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/grafana-plugin/CHANGELOG.md b/grafana-plugin/CHANGELOG.md new file mode 100644 index 00000000..8893332c --- /dev/null +++ b/grafana-plugin/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change Log + +## 0.0.71 (2022-06-06) + +- Initial Release \ No newline at end of file diff --git a/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx b/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx index 7d446ef1..7b2c23a6 100644 --- a/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx +++ b/grafana-plugin/src/containers/DefaultPageLayout/DefaultPageLayout.tsx @@ -100,7 +100,9 @@ const DefaultPageLayout: FC = observer((props) => { currentTeam && currentUser && store.isUserActionAllowed(UserAction.UpdateOwnSettings) && - (!currentUser.verified_phone_number || !currentUser.slack_user_identity) && + (!currentUser.verified_phone_number || + !currentUser.slack_user_identity || + currentUser.cloud_connection_status !== 3) && !getItem(AlertID.CONNECTIVITY_WARNING) ) && ( = observer((props) => { {'. '} )} - {!currentUser.verified_phone_number && 'Your phone number is not verified. '} + {currentUser.cloud_connection_status !== 3 && + !currentUser.verified_phone_number && + 'Your phone number is not verified. '} {currentTeam.slack_team_identity && !currentUser.slack_user_identity && 'Your slack account is not connected. '} diff --git a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx index 5ca6d87d..d3996c51 100644 --- a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx +++ b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx @@ -35,11 +35,10 @@ const cx = cn.bind(styles); interface Props extends PluginConfigPageProps> {} export const PluginConfigPage = (props: Props) => { - const grafanaUrlDefault = getItem('grafanaUrl') || window.location.origin; const { plugin } = props; const [onCallApiUrl, setOnCallApiUrl] = useState(getItem('onCallApiUrl')); const [onCallInvitationToken, setOnCallInvitationToken] = useState(); - const [grafanaUrl, setGrafanaUrl] = useState(grafanaUrlDefault); + const [grafanaUrl, setGrafanaUrl] = useState(getItem('grafanaUrl')); const [pluginConfigLoading, setPluginConfigLoading] = useState(true); const [pluginStatusOk, setPluginStatusOk] = useState(); const [pluginStatusMessage, setPluginStatusMessage] = useState(); @@ -298,10 +297,10 @@ Seek for such a line: “Your invite token: <> , use it in the Graf label="OnCall backend URL" description={ - It should be rechable from Grafana. Possible options:
- http://host.docker.internal:8000 (if you run backend in the docker locally) + It should be reachable from Grafana. Possible options:
+ http://host.docker.internal:8080 (if you run backend in the docker locally)
- http://localhost:8000
+ http://localhost:8080
...
} diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/CloudPhoneSettings/CloudPhoneSettings.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/CloudPhoneSettings/CloudPhoneSettings.tsx index 724b4712..74438027 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/CloudPhoneSettings/CloudPhoneSettings.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/CloudPhoneSettings/CloudPhoneSettings.tsx @@ -48,7 +48,7 @@ const CloudPhoneSettings = observer((props: CloudPhoneSettingsProps) => { }, []); const handleLinkClick = (link: string) => { - window.location.replace(link); + window.open(link, '_blank'); }; const syncUser = async () => { diff --git a/grafana-plugin/src/index.css b/grafana-plugin/src/index.css index eeeff2de..bdfece87 100644 --- a/grafana-plugin/src/index.css +++ b/grafana-plugin/src/index.css @@ -30,12 +30,19 @@ background: var(--highlighted-row-bg); } +/* This is for Grafana 8, remove later */ @media (max-width: 1540px) { .page-header__tabs > ul > li > a > div { display: none; } } +@media (max-width: 1540px) { + .page-header__tabs > div > div > a > div { + display: none; + } +} + @media (max-width: 1300px) { .sidemenu { position: fixed !important; diff --git a/grafana-plugin/src/pages/cloud/CloudPage.tsx b/grafana-plugin/src/pages/cloud/CloudPage.tsx index d81ce0c9..63df35e0 100644 --- a/grafana-plugin/src/pages/cloud/CloudPage.tsx +++ b/grafana-plugin/src/pages/cloud/CloudPage.tsx @@ -69,10 +69,6 @@ const CloudPage = observer((props: CloudPageProps) => { setApiKeyError(false); }, []); - const saveKeyAndConnect = () => { - setShowConfirmationModal(true); - }; - const disconnectCloudOncall = () => { setCloudIsConnected(false); store.cloudStore.disconnectToCloud(); @@ -105,7 +101,7 @@ const CloudPage = observer((props: CloudPageProps) => { }; const handleLinkClick = (link: string) => { - window.location.replace(link); + window.open(link, '_blank'); }; const renderButtons = (user: Cloud) => { @@ -130,10 +126,9 @@ const CloudPage = observer((props: CloudPageProps) => { return ( @@ -343,7 +338,7 @@ const CloudPage = observer((props: CloudPageProps) => { > - @@ -387,23 +382,6 @@ const CloudPage = observer((props: CloudPageProps) => { ) : ( DisconnectedBlock )} - - {showConfirmationModal && ( - setShowConfirmationModal(false)} - > - - - - - - )} ); diff --git a/grafana-plugin/src/pages/livesettings/LiveSettings.module.css b/grafana-plugin/src/pages/livesettings/LiveSettings.module.css index bbeeab7a..7bdf528a 100644 --- a/grafana-plugin/src/pages/livesettings/LiveSettings.module.css +++ b/grafana-plugin/src/pages/livesettings/LiveSettings.module.css @@ -22,3 +22,8 @@ .description-style > a { color: var(--primary-text-link); } + +.description-style { + word-wrap: break-word; + word-break: break-word; +} diff --git a/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx b/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx index af261122..9542253f 100644 --- a/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx +++ b/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx @@ -14,7 +14,9 @@ import { } from '@grafana/ui'; import cn from 'classnames/bind'; import { omit } from 'lodash-es'; +import { observe } from 'mobx'; import { observer } from 'mobx-react'; +import { Lambda } from 'mobx/lib/internal'; import { AlignType } from 'rc-table/lib/interface'; import { Redirect } from 'react-router-dom'; @@ -46,6 +48,23 @@ class LiveSettings extends React.Component hideValues: true, }; + disposer: Lambda; + + constructor(props: LiveSettingsProps) { + super(props); + + const { store } = props; + + this.disposer = observe(store.userStore, (change) => { + if (change.name === 'currentUserPk') { + this.update(); + } + }); + } + + componentWillUnmount() { + this.disposer(); + } componentDidMount() { this.update(); } diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index c5e77b2a..d2ad118c 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -1,4 +1,4 @@ import plugin from '../../package.json'; // eslint-disable-line export const APP_TITLE = 'Grafana OnCall'; -export const APP_SUBTITLE = `Incident Response (${plugin?.version})`; +export const APP_SUBTITLE = `Developer-friendly incident response (${plugin?.version})`;