application_row.vue 11.3 KB
Newer Older
1
<script>
2
/* eslint-disable vue/require-default-prop */
3
import { GlLink, GlModalDirective } from '@gitlab/ui';
4
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
5 6 7 8
import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
9
import UninstallApplicationButton from './uninstall_application_button.vue';
10
import UninstallApplicationConfirmationModal from './uninstall_application_confirmation_modal.vue';
11

12
import { APPLICATION_STATUS } from '../constants';
13

14 15 16 17
export default {
  components: {
    loadingButton,
    identicon,
18 19
    TimeagoTooltip,
    GlLink,
20
    UninstallApplicationButton,
21 22 23 24
    UninstallApplicationConfirmationModal,
  },
  directives: {
    GlModalDirective,
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
  },
  props: {
    id: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
    titleLink: {
      type: String,
      required: false,
    },
    manageLink: {
      type: String,
      required: false,
    },
    logoUrl: {
      type: String,
      required: false,
      default: null,
    },
    disabled: {
      type: Boolean,
      required: false,
      default: false,
    },
53 54 55 56 57
    uninstallable: {
      type: Boolean,
      required: false,
      default: false,
    },
58 59 60 61 62 63 64 65 66 67 68 69
    status: {
      type: String,
      required: false,
    },
    statusReason: {
      type: String,
      required: false,
    },
    requestReason: {
      type: String,
      required: false,
    },
70 71 72
    installed: {
      type: Boolean,
      required: false,
73
      default: false,
74
    },
75 76 77 78 79
    installFailed: {
      type: Boolean,
      required: false,
      default: false,
    },
80 81 82 83 84 85 86 87 88 89 90 91
    version: {
      type: String,
      required: false,
    },
    chartRepo: {
      type: String,
      required: false,
    },
    upgradeAvailable: {
      type: Boolean,
      required: false,
    },
92 93 94 95 96 97 98 99 100 101
    updateSuccessful: {
      type: Boolean,
      required: false,
      default: false,
    },
    updateFailed: {
      type: Boolean,
      required: false,
      default: false,
    },
102 103 104 105 106 107 108 109 110 111
    uninstallFailed: {
      type: Boolean,
      required: false,
      default: false,
    },
    uninstallSuccessful: {
      type: Boolean,
      required: false,
      default: false,
    },
112 113 114 115 116
    updateAcknowledged: {
      type: Boolean,
      required: false,
      default: true,
    },
117 118 119 120 121 122 123 124 125 126 127 128 129
    installApplicationRequestParams: {
      type: Object,
      required: false,
      default: () => ({}),
    },
  },
  computed: {
    isUnknownStatus() {
      return !this.isKnownStatus && this.status !== null;
    },
    isKnownStatus() {
      return Object.values(APPLICATION_STATUS).includes(this.status);
    },
130
    isInstalling() {
131
      return this.status === APPLICATION_STATUS.INSTALLING;
132
    },
133 134 135 136 137 138 139
    canInstall() {
      return (
        this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
        this.status === APPLICATION_STATUS.INSTALLABLE ||
        this.isUnknownStatus
      );
    },
140 141 142 143 144 145 146 147 148 149
    hasLogo() {
      return !!this.logoUrl;
    },
    identiconId() {
      // generate a deterministic integer id for the identicon background
      return this.id.charCodeAt(0);
    },
    rowJsClass() {
      return `js-cluster-application-row-${this.id}`;
    },
150 151 152 153 154 155
    displayUninstallButton() {
      return this.installed && this.uninstallable;
    },
    displayInstallButton() {
      return !this.installed || !this.uninstallable;
    },
156
    installButtonLoading() {
157
      return !this.status || this.isInstalling;
158 159 160 161 162 163 164 165
    },
    installButtonDisabled() {
      // Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
      // we already made a request to install and are just waiting for the real-time
      // to sync up.
      return (
        ((this.status !== APPLICATION_STATUS.INSTALLABLE &&
          this.status !== APPLICATION_STATUS.ERROR) ||
166
          this.isInstalling) &&
167 168 169 170 171
        this.isKnownStatus
      );
    },
    installButtonLabel() {
      let label;
172
      if (this.canInstall) {
173
        label = s__('ClusterIntegration|Install');
174
      } else if (this.isInstalling) {
175
        label = s__('ClusterIntegration|Installing');
176
      } else if (this.installed) {
177 178
        label = s__('ClusterIntegration|Installed');
      }
179

180 181 182 183 184 185 186 187
      return label;
    },
    showManageButton() {
      return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
    },
    manageButtonLabel() {
      return s__('ClusterIntegration|Manage');
    },
188 189 190
    hasError() {
      return this.installFailed || this.uninstallFailed;
    },
191
    generalErrorDescription() {
192 193 194 195 196 197 198 199 200 201 202
      let errorDescription;

      if (this.installFailed) {
        errorDescription = s__('ClusterIntegration|Something went wrong while installing %{title}');
      } else if (this.uninstallFailed) {
        errorDescription = s__(
          'ClusterIntegration|Something went wrong while uninstalling %{title}',
        );
      }

      return sprintf(errorDescription, { title: this.title });
203
    },
204
    versionLabel() {
205
      if (this.updateFailed) {
206 207 208 209 210 211 212 213
        return s__('ClusterIntegration|Upgrade failed');
      } else if (this.isUpgrading) {
        return s__('ClusterIntegration|Upgrading');
      }

      return s__('ClusterIntegration|Upgraded');
    },
    upgradeFailureDescription() {
J
jerasmus 已提交
214
      return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
215 216 217 218 219 220 221 222
    },
    upgradeSuccessDescription() {
      return sprintf(s__('ClusterIntegration|%{title} upgraded successfully.'), {
        title: this.title,
      });
    },
    upgradeButtonLabel() {
      let label;
223
      if (this.upgradeAvailable && !this.updateFailed && !this.isUpgrading) {
224 225
        label = s__('ClusterIntegration|Upgrade');
      } else if (this.isUpgrading) {
J
jerasmus 已提交
226
        label = s__('ClusterIntegration|Updating');
227
      } else if (this.updateFailed) {
J
jerasmus 已提交
228
        label = s__('ClusterIntegration|Retry update');
229 230 231 232 233 234
      }

      return label;
    },
    isUpgrading() {
      // Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend
235
      return this.status === APPLICATION_STATUS.UPDATING;
236
    },
J
jerasmus 已提交
237 238 239 240
    shouldShowUpgradeDetails() {
      // This method only returns true when;
      // Upgrade was successful OR Upgrade failed
      //     AND new upgrade is unavailable AND version information is present.
241
      return (this.updateSuccessful || this.updateFailed) && !this.upgradeAvailable && this.version;
J
jerasmus 已提交
242
    },
243 244 245 246 247
    uninstallSuccessDescription() {
      return sprintf(s__('ClusterIntegration|%{title} uninstalled successfully.'), {
        title: this.title,
      });
    },
248 249
  },
  watch: {
250 251
    updateSuccessful(updateSuccessful) {
      if (updateSuccessful) {
J
Jacques Erasmus 已提交
252
        this.$toast.show(this.upgradeSuccessDescription);
253 254
      }
    },
255 256 257 258 259
    uninstallSuccessful(uninstallSuccessful) {
      if (uninstallSuccessful) {
        this.$toast.show(this.uninstallSuccessDescription);
      }
    },
260 261 262 263 264 265 266 267
  },
  methods: {
    installClicked() {
      eventHub.$emit('installApplication', {
        id: this.id,
        params: this.installApplicationRequestParams,
      });
    },
268 269 270 271 272 273
    upgradeClicked() {
      eventHub.$emit('upgradeApplication', {
        id: this.id,
        params: this.installApplicationRequestParams,
      });
    },
274 275 276 277 278
    uninstallConfirmed() {
      eventHub.$emit('uninstallApplication', {
        id: this.id,
      });
    },
279 280
  },
};
281 282 283 284
</script>

<template>
  <div
285 286
    :class="[
      rowJsClass,
287
      installed && 'cluster-application-installed',
M
Mike Greiling 已提交
288
      disabled && 'cluster-application-disabled',
289 290
    ]"
    class="cluster-application-row gl-responsive-table-row gl-responsive-table-row-col-span"
291
  >
M
Mike Greiling 已提交
292 293
    <div class="gl-responsive-table-row-layout" role="row">
      <div class="table-section append-right-8 section-align-top" role="gridcell">
294 295 296 297 298 299
        <img
          v-if="hasLogo"
          :src="logoUrl"
          :alt="`${title} logo`"
          class="cluster-application-logo avatar s40"
        />
M
Mike Greiling 已提交
300
        <identicon v-else :entity-id="identiconId" :entity-name="title" size-class="s40" />
301
      </div>
M
Mike Greiling 已提交
302
      <div class="table-section cluster-application-description section-wrap" role="gridcell">
303 304 305 306 307 308 309
        <strong>
          <a
            v-if="titleLink"
            :href="titleLink"
            target="blank"
            rel="noopener noreferrer"
            class="js-cluster-application-title"
310
            >{{ title }}</a
311
          >
312
          <span v-else class="js-cluster-application-title">{{ title }}</span>
313
        </strong>
314
        <slot name="description"></slot>
315
        <div v-if="hasError" class="cluster-application-error text-danger prepend-top-10">
316 317 318 319
          <p class="js-cluster-application-general-error-message append-bottom-0">
            {{ generalErrorDescription }}
          </p>
          <ul v-if="statusReason || requestReason">
M
Mike Greiling 已提交
320
            <li v-if="statusReason" class="js-cluster-application-status-error-message">
321 322
              {{ statusReason }}
            </li>
M
Mike Greiling 已提交
323
            <li v-if="requestReason" class="js-cluster-application-request-error-message">
324 325 326 327
              {{ requestReason }}
            </li>
          </ul>
        </div>
328 329

        <div
J
jerasmus 已提交
330
          v-if="shouldShowUpgradeDetails"
331 332 333
          class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
        >
          {{ versionLabel }}
334
          <span v-if="updateSuccessful">to</span>
335 336

          <gl-link
337
            v-if="updateSuccessful"
338 339 340
            :href="chartRepo"
            target="_blank"
            class="js-cluster-application-upgrade-version"
341
            >chart v{{ version }}</gl-link
342 343 344 345
          >
        </div>

        <div
346
          v-if="updateFailed && !isUpgrading"
347 348 349 350 351
          class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-upgrade-failure-message"
        >
          {{ upgradeFailureDescription }}
        </div>
        <loading-button
352
          v-if="upgradeAvailable || updateFailed || isUpgrading"
353 354 355 356 357 358
          class="btn btn-primary js-cluster-application-upgrade-button mt-2"
          :loading="isUpgrading"
          :disabled="isUpgrading"
          :label="upgradeButtonLabel"
          @click="upgradeClicked"
        />
359 360
      </div>
      <div
361
        :class="{ 'section-25': showManageButton, 'section-15': !showManageButton }"
362
        class="table-section table-button-footer section-align-top"
363 364
        role="gridcell"
      >
M
Mike Greiling 已提交
365
        <div v-if="showManageButton" class="btn-group table-action-buttons">
366 367 368
          <a :href="manageLink" :class="{ disabled: disabled }" class="btn">{{
            manageButtonLabel
          }}</a>
369
        </div>
370 371
        <div class="btn-group table-action-buttons">
          <loading-button
372
            v-if="displayInstallButton"
373
            :loading="installButtonLoading"
374
            :disabled="disabled || installButtonDisabled"
375
            :label="installButtonLabel"
376
            class="js-cluster-application-install-button"
377 378
            @click="installClicked"
          />
379 380
          <uninstall-application-button
            v-if="displayUninstallButton"
381 382
            v-gl-modal-directive="'uninstall-' + id"
            :status="status"
383 384
            class="js-cluster-application-uninstall-button"
          />
385 386 387 388 389
          <uninstall-application-confirmation-modal
            :application="id"
            :application-title="title"
            @confirm="uninstallConfirmed()"
          />
390 391 392 393 394
        </div>
      </div>
    </div>
  </div>
</template>