"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.eventsBadge = exports.eventsMode = void 0;

var React = _interopRequireWildcard(require("react"));

var prettyPrintDuration = _interopRequireWildcard(require("pretty-ms"));

var _core = require("@kui-shell/core");

var _pluginClientCommon = require("@kui-shell/plugin-client-common");

var _cssForValue = _interopRequireDefault(require("../css-for-value"));

var _util = require("../../util/util");

var _trafficLight = _interopRequireDefault(require("../../model/traffic-light"));

var _ContainerCommon = require("./ContainerCommon");

var _exec = require("../../../controller/kubectl/exec");

var _resource = require("../../model/resource");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

/*
 * Copyright 2020 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }

  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }

    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }

    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }

    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};

const strings = (0, _core.i18n)('plugin-kubectl');
const strings2 = (0, _core.i18n)('plugin-kubectl', 'events');
/** Interval in milliseconds with which we update the "ago" timestamps */

const UPDATE_INTERVAL = 60 * 1000;
/** Parse a stream of JSON-formatted bits into a stream of ObservedEvent */

function StreamingJSONParser(cb) {
  let escaping = false;
  let inQuotes = false;
  let depth = 0;
  let bundle = '';
  return _ => {
    const data = _.toString();

    for (let idx = 0; idx < data.length; idx++) {
      const ch = data.charAt(idx);
      const escaped = escaping;
      escaping = false;
      bundle += ch;

      if (!inQuotes && ch === '{') {
        depth++;
      }

      if (!escaped && ch === '"') {
        inQuotes = !inQuotes;
      }

      if (!escaped && ch === '\\') {
        escaping = true;
      }

      if (!inQuotes && ch === '}') {
        if (--depth === 0) {
          const event = JSON.parse(bundle); // parse the lastTimestamp ahead of time

          const oevent = Object.assign(event, {
            lastTimestamp: new Date(event.lastTimestamp).getTime()
          });
          cb(oevent);
          bundle = '';
        }
      }
    }
  };
}

class Events extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      now: this.now(),
      timer: setInterval(this.updateNow.bind(this), UPDATE_INTERVAL),
      job: undefined,
      isLive: false,
      waitingForHysteresis: false,
      events: []
    };
    this.initStream();
  }

  now() {
    return new Date().getTime();
  }

  updateNow() {
    this.setState({
      now: this.now()
    });
  }

  componentWillUnmount() {
    clearInterval(this.state.timer);

    if (this.state.job) {
      this.state.job.abort();
    }
  }
  /** Toolbar text to display */


  toolbarText(status) {
    return {
      type: status === 'Live' ? 'info' : status === 'Paused' ? 'warning' : 'error',
      text: strings2(status === 'Live' ? 'Events are live streaming' : status === 'Paused' ? 'Event streaming is paused' : status === 'Stopped' ? 'Event streaming stopped' : 'Event streaming stopped abnormally')
    };
  }

  toolbarButtonsForError(status) {
    if (status === 'Error') {
      return [{
        mode: 'retry-streaming',
        label: strings('Retry'),
        kind: 'view',
        icon: React.createElement(_pluginClientCommon.Icons, {
          icon: "Retry",
          onClick: () => this.initStream()
        }),
        command: () => {} // eslint-disable-line @typescript-eslint/no-empty-function

      }];
    } else {
      return [];
    }
  }

  toolbarButtonsForStreaming(status) {
    const isLive = status === 'Live';
    return [{
      mode: 'toggle-streaming',
      label: isLive ? strings('Pause Streaming') : strings('Resume Streaming'),
      kind: 'view',
      icon: React.createElement(_pluginClientCommon.Icons, {
        icon: isLive ? 'Pause' : 'Play'
      }),
      command: this.toggleStreaming.bind(this, !isLive)
    }];
  }
  /** Toolbar buttons to display */


  toolbarButtons(status) {
    return this.toolbarButtonsForError(status).concat(this.toolbarButtonsForStreaming(status));
  }
  /** Update Toolbar text and Toolbar buttons. */


  updateToolbar(status) {
    this.props.toolbarController.willUpdateToolbar(this.toolbarText(status), this.toolbarButtons(status), true // replace default buttons
    );
  }
  /** Update our State to reflect the new ObservedEvents. */


  observe(newlyObservedEvents) {
    this.setState(curState => ({
      events: curState.events.concat(newlyObservedEvents).sort((a, b) => b.lastTimestamp - a.lastTimestamp)
    }));
  }
  /** The part of toggleStreaming that deals with PTY flow control. */


  doFlowControl(desiredStateIsLive = this.state.isLive) {
    if (this.state.job) {
      if (desiredStateIsLive) {
        this.state.job.xon();
      } else {
        this.state.job.xoff();
      }
    }
  }
  /** Handler for Pause/Play. */


  toggleStreaming(desiredState) {
    if (this.state.isLive !== desiredState) {
      this.doFlowControl(desiredState);
      this.updateToolbar(desiredState ? 'Live' : 'Paused');
      this.setState(curState => {
        if (curState.isLive !== desiredState) {
          return {
            isLive: desiredState
          };
        }
      });
    }
  }
  /** Initialize the event stream */


  initStream() {
    const args = this.command({
      /** prior to PTY initialization, we provide a streaming consumer */
      onInit: () => {
        // this is our streaming consumer
        return StreamingJSONParser(event => this.observe([event]));
      },
      onReady: job => {
        this.updateToolbar('Live');
        setTimeout(() => this.setState({
          waitingForHysteresis: false
        }), _ContainerCommon.HYSTERESIS);
        this.setState({
          isLive: true,
          job,
          waitingForHysteresis: true
        });
      },
      onExit: exitCode => {
        console.error('event stream exited with code', exitCode);
        this.updateToolbar(exitCode === 0 ? 'Stopped' : 'Error');
      }
    });
    (0, _exec.doExecWithPty)(args);
  }

  command(execOptions) {
    const {
      args,
      resource
    } = this.props; // limit events to those intersecting with the giving resource

    const filter = `involvedObject.apiVersion=${resource.apiVersion},involvedObject.kind=${resource.kind},involvedObject.name=${resource.metadata.name},involvedObject.namespace=${resource.metadata.namespace}`; // this is the command that will fetch the events table; we specify a watchable table
    // remove the OBJECT column, since we're filtering down to one object

    const argv = [(0, _util.getCommandFromArgs)(args), 'get', 'events', '--field-selector', filter, '-n', resource.metadata.namespace, Events.format, '--watch'];
    const argvNoOptions = argv.filter(_ => _[0] !== '-');
    const parsedOptions = {
      n: resource.metadata.namespace,
      'field-selector': filter,
      watch: true
    };
    const command = argv.join(' ');
    return Object.assign({}, this.props.args, {
      REPL: this.props.repl,
      command,
      argv,
      argvNoOptions,
      parsedOptions,
      execOptions
    });
  }
  /** Render the events in the case we not yet finished initializing. */


  notDoneLoading() {
    return React.createElement(_pluginClientCommon.Loading, null);
  }
  /** Render the events in the case we no logs to show. */


  nothingToShow() {
    return React.createElement("div", {
      className: "kui--hero-text"
    }, strings2('No events'));
  }

  render() {
    if (!this.state.job || this.state.waitingForHysteresis) {
      return this.notDoneLoading();
    } else if (!this.state.events || this.state.events.length === 0) {
      return this.nothingToShow();
    }

    return React.createElement("div", {
      className: "scrollable scrollable-auto"
    }, React.createElement("div", {
      className: "bx--data-table kui--kubectl-events-grid"
    }, this.state.events.map((event, idx) => React.createElement(EventUI, Object.assign({}, event, {
      now: this.state.now,
      key: idx
    }, this.props)))));
  }

}

Events.format = '-o json';
/** Render one event */

class EventUI extends React.PureComponent {
  age() {
    const {
      lastTimestamp,
      now
    } = this.props;
    const delta = lastTimestamp - now;
    let ago = '0';

    try {
      ago = strings2('ago', prettyPrintDuration(Math.max(1, -delta), {
        compact: true
      }));
    } catch (err) {
      console.error('error parsing lastTimestamp', lastTimestamp, err);
    }

    return React.createElement("span", {
      className: "kui--kubectl-event-record-age smaller-text sub-text no-wrap left-pad"
    }, ago);
  }

  reason() {
    const {
      reason
    } = this.props;
    return React.createElement("div", {
      "data-tag": "badge"
    }, React.createElement("span", {
      title: reason,
      "data-tag": "badge-circle",
      className: _cssForValue.default[reason]
    }), React.createElement("span", {
      className: "kui--cell-inner-text smaller-text"
    }, reason));
  }

  message() {
    return React.createElement("pre", {
      className: "kui--kubectl-event-record-message pre-wrap break-all"
    }, this.props.message);
  }

  source() {
    const {
      args,
      repl,
      source: {
        component,
        host
      }
    } = this.props;

    const onClick = () => {
      const cmd = `${(0, _util.getCommandFromArgs)(args)} get node ${host} -o yaml`;
      repl.pexec(cmd);
    };

    return React.createElement("span", {
      className: "smaller-text"
    }, strings2('Generated by component X on host', component), React.createElement("a", {
      className: "small-left-pad",
      href: "#",
      onClick: onClick
    }, host));
  }

  clickForDetails() {
    const onClick = () => {
      const {
        args,
        repl,
        metadata: {
          name,
          namespace
        }
      } = this.props;
      const cmd = `${(0, _util.getCommandFromArgs)(args)} get event ${name} -n ${namespace} -o yaml`;
      repl.pexec(cmd);
    };

    return React.createElement("a", {
      className: "smaller-text",
      href: "#",
      onClick: onClick
    }, strings2('Details'));
  }

  render() {
    return React.createElement("div", {
      className: "kui--kubectl-event-record smaller-text"
    }, React.createElement("div", {
      className: "flex-layout"
    }, React.createElement("span", {
      className: "flex-fill"
    }, this.reason()), this.age()), this.source(), React.createElement("div", {
      className: "flex-layout full-height flex-align-stretch top-pad"
    }, React.createElement("div", {
      className: "flex-layout"
    }, React.createElement("span", {
      className: "flex-fill"
    }, this.message()), this.clickForDetails())));
  }

}
/**
 * Extract the events
 *
 */


function content({
  REPL
}, resource, args) {
  return __awaiter(this, void 0, void 0, function* () {
    return {
      react: function LogsProvider(toolbarController) {
        return React.createElement(Events, {
          repl: REPL,
          resource: resource,
          args: args,
          toolbarController: toolbarController
        });
      }
    };
  });
}
/**
 * @return whether the given resource might possibly have events;
 * since Events never have Events, we can exclude those always
 *
 */


function hasEvents(resource) {
  return (0, _resource.isCrudableKubeResource)(resource) && !(0, _resource.isEvent)(resource) && (0, _resource.isNamespaced)(resource);
}
/**
 * Add a Events mode button to the given modes model, if called for by
 * the given resource.
 *
 */


const eventsMode = {
  when: hasEvents,
  mode: {
    mode: 'events',
    label: strings('events'),
    content
  }
};
exports.eventsMode = eventsMode;
const eventsBadge = {
  when: _resource.isEvent,
  badge: event => {
    const cssFromReason = _cssForValue.default[event.reason];
    return {
      title: cssFromReason ? event.reason : event.type,
      css: cssFromReason || (event.type === 'Error' ? _trafficLight.default.Red : event.type === 'Warning' ? _trafficLight.default.Yellow : undefined)
    };
  }
};
exports.eventsBadge = eventsBadge;