import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import * as React from 'react';
import { Subject, Subscription } from 'rxjs';
import { catchError, concatMap, delay, mergeMap, retryWhen, tap, timeout } from 'rxjs/operators';
import { PageTitle } from '../components/PageTitle';
import siteSchemaJSON from '../schema/site.schema.json';
import { DynamicallyImportedMonacoSettingsEditor } from '../settings/DynamicallyImportedMonacoSettingsEditor';
import { refreshSiteFlags } from '../site/backend';
import { eventLogger } from '../tracking/eventLogger';
import { fetchSite, reloadSite, updateSiteConfiguration } from './backend';
import { siteConfigActions } from './configHelpers';
const EXPECTED_RELOAD_WAIT = 7 * 1000; // 7 seconds
/**
 * A page displaying the site configuration.
 */
export class SiteAdminConfigurationPage extends React.Component {
    constructor() {
        super(...arguments);
        this.state = {
            loading: true,
            restartToApply: window.context.needServerRestart,
        };
        this.remoteRefreshes = new Subject();
        this.remoteUpdates = new Subject();
        this.siteReloads = new Subject();
        this.subscriptions = new Subscription();
        this.onDirtyChange = (isDirty) => this.setState({ isDirty });
        this.onSave = (value) => {
            eventLogger.log('SiteConfigurationSaved');
            this.remoteUpdates.next(value);
        };
        this.reloadSite = () => {
            eventLogger.log('SiteReloaded');
            this.siteReloads.next();
        };
    }
    componentDidMount() {
        eventLogger.logViewEvent('SiteAdminConfiguration');
        this.subscriptions.add(this.remoteRefreshes.pipe(mergeMap(() => fetchSite())).subscribe(site => this.setState({
            site,
            error: undefined,
            loading: false,
        }), error => this.setState({ error, loading: false })));
        this.remoteRefreshes.next();
        this.subscriptions.add(this.remoteUpdates
            .pipe(tap(() => this.setState({ saving: true, error: undefined })), concatMap(event => updateSiteConfiguration(event).pipe(catchError(error => {
            console.error(error);
            this.setState({ saving: false, error });
            return [];
        }))), tap(restartToApply => {
            if (restartToApply) {
                window.context.needServerRestart = restartToApply;
            }
            else {
                // Refresh site flags so that global site alerts
                // reflect the latest configuration.
                refreshSiteFlags().subscribe(undefined, err => console.error(err));
            }
            this.setState({ restartToApply });
            this.remoteRefreshes.next();
        }))
            .subscribe(() => this.setState({ saving: false }), error => this.setState({ saving: false, error })));
        this.subscriptions.add(this.siteReloads
            .pipe(tap(() => this.setState({ reloadStartedAt: Date.now(), error: undefined })), mergeMap(reloadSite), delay(2000), mergeMap(() => 
        // wait for server to restart
        fetchSite().pipe(retryWhen(x => x.pipe(tap(() => this.forceUpdate()), delay(500))), timeout(10000))), tap(() => this.remoteRefreshes.next()))
            .subscribe(() => {
            this.setState({ reloadStartedAt: undefined });
            window.location.reload(); // brute force way to reload view state
        }, error => this.setState({ reloadStartedAt: undefined, error })));
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    render() {
        const alerts = [];
        if (this.state.error) {
            alerts.push(React.createElement("div", { key: "error", className: "alert alert-danger site-admin-configuration-page__alert" },
                React.createElement("p", null,
                    "Error: ",
                    this.state.error.message)));
        }
        if (this.state.reloadStartedAt) {
            alerts.push(React.createElement("div", { key: "error", className: "alert alert-primary site-admin-configuration-page__alert" },
                React.createElement("p", null,
                    React.createElement(LoadingSpinner, { className: "icon-inline" }),
                    " Waiting for site to reload..."),
                Date.now() - this.state.reloadStartedAt > EXPECTED_RELOAD_WAIT && (React.createElement("p", null,
                    React.createElement("small", null, "It's taking longer than expected. Check the server logs for error messages.")))));
        }
        if (this.state.restartToApply) {
            alerts.push(React.createElement("div", { key: "remote-dirty", className: "alert alert-warning site-admin-configuration-page__alert site-admin-configuration-page__alert-flex" },
                "Server restart is required for the configuration to take effect.",
                React.createElement("button", { className: "btn btn-primary btn-sm", onClick: this.reloadSite }, "Restart server")));
        }
        if (this.state.site && this.state.site.configuration && !this.state.site.configuration.canUpdate) {
            alerts.push(React.createElement("div", { key: "volatile", className: "alert alert-danger site-admin-configuration-page__alert" },
                React.createElement("p", null,
                    "Use this editor as a scratch area for composing Sourcegraph site configuration.",
                    ' ',
                    React.createElement("strong", null, "Changes will NOT be saved"),
                    " and will be lost when you leave this page."),
                React.createElement("p", null,
                    React.createElement("small", null,
                        "To save and apply changes, manually update",
                        ' ',
                        formatEnvVar(this.state.site.configuration.source),
                        " (or ",
                        React.createElement("code", null, "values.yaml"),
                        " for Kubernetes cluster deployments) with the configuration below and restart the server. Online configuration editing is only supported when deployed on Docker and when the configuration lives in a writable file on disk."))));
        }
        if (this.state.site &&
            this.state.site.configuration &&
            this.state.site.configuration.validationMessages &&
            this.state.site.configuration.validationMessages.length > 0) {
            alerts.push(React.createElement("div", { key: "validation-messages", className: "alert alert-danger site-admin-configuration-page__alert" },
                React.createElement("p", null, "The server reported issues in the last-saved config:"),
                React.createElement("ul", null, this.state.site.configuration.validationMessages.map((e, i) => (React.createElement("li", { key: i, className: "site-admin-configuration-page__alert-item" }, e))))));
        }
        // Avoid user confusion with values.yaml properties mixed in with site config properties.
        const contents = this.state.site && this.state.site.configuration && this.state.site.configuration.effectiveContents;
        const legacyKubernetesConfigProps = [
            'alertmanagerConfig',
            'alertmanagerURL',
            'authProxyIP',
            'authProxyPassword',
            'deploymentOverrides',
            'gitoliteIP',
            'gitserverCount',
            'gitserverDiskSize',
            'gitserverSSH',
            'httpNodePort',
            'httpsNodePort',
            'indexedSearchDiskSize',
            'langGo',
            'langJava',
            'langJavaScript',
            'langPHP',
            'langPython',
            'langSwift',
            'langTypeScript',
            'nodeSSDPath',
            'phabricatorIP',
            'prometheus',
            'pyPIIP',
            'rbac',
            'storageClass',
            'useAlertManager',
        ].filter(prop => contents && contents.includes(`"${prop}"`));
        if (legacyKubernetesConfigProps.length > 0) {
            alerts.push(React.createElement("div", { key: "legacy-cluster-props-present", className: "alert alert-info site-admin-configuration-page__alert" },
                "The configuration contains properties that are valid only in the",
                React.createElement("code", null, "values.yaml"),
                " config file used for Kubernetes cluster deployments of Sourcegraph:",
                ' ',
                React.createElement("code", null, legacyKubernetesConfigProps.join(' ')),
                ". You can disregard the validation warnings for these properties reported by the configuration editor."));
        }
        const isReloading = typeof this.state.reloadStartedAt === 'number';
        return (React.createElement("div", { className: "site-admin-configuration-page" },
            React.createElement(PageTitle, { title: "Configuration - Admin" }),
            React.createElement("h2", null, "Site configuration"),
            React.createElement("p", null,
                "View and edit the Sourcegraph site configuration. See",
                ' ',
                React.createElement("a", { href: "https://about.sourcegraph.com/docs/server/" }, "documentation"),
                " for more information."),
            React.createElement("div", { className: "site-admin-configuration-page__alerts" }, alerts),
            this.state.loading && React.createElement(LoadingSpinner, { className: "icon-inline" }),
            this.state.site &&
                this.state.site.configuration && (React.createElement("div", null,
                React.createElement(DynamicallyImportedMonacoSettingsEditor, { value: contents || '', actions: siteConfigActions, jsonSchema: siteSchemaJSON, onDirtyChange: this.onDirtyChange, canEdit: this.state.site.configuration.canUpdate, saving: this.state.saving, loading: isReloading || this.state.saving, height: 600, isLightTheme: this.props.isLightTheme, onSave: this.onSave, history: this.props.history }),
                React.createElement("p", { className: "form-text text-muted" },
                    React.createElement("small", null,
                        "Source: ",
                        formatEnvVar(this.state.site.configuration.source))),
                React.createElement("p", { className: "form-text text-muted" },
                    React.createElement("small", null,
                        "Use Ctrl+Space for completion, and hover over JSON properties for documentation. For more information, see the",
                        ' ',
                        React.createElement("a", { href: "https://about.sourcegraph.com/docs/server/config/settings" }, "documentation"),
                        "."))))));
    }
}
function formatEnvVar(text) {
    const S = 'SOURCEGRAPH_CONFIG';
    const idx = text.indexOf(S);
    if (idx === -1) {
        return text;
    }
    return [text.slice(0, idx), React.createElement("code", { key: S }, S), text.slice(idx + S.length)];
}
//# sourceMappingURL=SiteAdminConfigurationPage.js.map