DEV: Modernize upgrade-show and repo model

fixup upgrade-show controller
This commit is contained in:
Jarek Radosz 2023-01-22 20:19:31 +01:00
parent 4e784351ad
commit 9163b3d2e2
8 changed files with 256 additions and 245 deletions

View File

@ -17,7 +17,7 @@ export default Controller.extend({
),
allUpToDate: computed("model.@each.upToDate", function () {
return this.get("model").every((repo) => repo.get("upToDate"));
return this.get("model").every((repo) => repo.upToDate);
}),
actions: {

View File

@ -1,66 +1,72 @@
import Repo from "discourse/plugins/docker_manager/discourse/models/repo";
import Controller from "@ember/controller";
import { equal } from "@ember/object/computed";
import { computed } from "@ember/object";
import { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import { equal } from "@ember/object/computed";
import { action } from "@ember/object";
export default Controller.extend({
dialog: service(),
export default class UpgradeShow extends Controller {
@service messageBus;
@service dialog;
output: null,
@tracked output = "";
@tracked status = null;
@tracked percent = 0;
init() {
this._super();
this.reset();
},
@equal("status", "complete") complete;
@equal("status", "failed") failed;
complete: equal("status", "complete"),
failed: equal("status", "failed"),
get multiUpgrade() {
return this.model.length !== 1;
}
multiUpgrade: computed("model.length", function () {
return this.get("model.length") !== 1;
}),
get title() {
return this.multiUpgrade ? "All" : this.model[0].name;
}
title: computed("model.@each.name", function () {
return this.get("multiUpgrade") ? "All" : this.get("model")[0].get("name");
}),
get isUpToDate() {
return this.model.every((repo) => repo.upToDate);
}
isUpToDate: computed("model.@each.upToDate", function () {
return this.get("model").every((repo) => repo.get("upToDate"));
}),
get upgrading() {
return this.model.some((repo) => repo.upgrading);
}
upgrading: computed("model.@each.upgrading", function () {
return this.get("model").some((repo) => repo.get("upgrading"));
}),
get repos() {
return this.isMultiple ? this.model : [this.model];
}
repos() {
const model = this.get("model");
return this.get("isMultiple") ? model : [model];
},
get upgradeButtonText() {
if (this.upgrading) {
return "Upgrading...";
} else {
return "Start Upgrading";
}
}
updateAttribute(key, value, valueIsKey = false) {
this.get("model").forEach((repo) => {
value = valueIsKey ? repo.get(value) : value;
repo.set(key, value);
this.model.forEach((repo) => {
value = valueIsKey ? repo[value] : value;
repo[key] = value;
});
},
}
messageReceived(msg) {
switch (msg.type) {
case "log":
this.set("output", this.get("output") + msg.value + "\n");
this.output = this.output + msg.value + "\n";
break;
case "percent":
this.set("percent", msg.value);
this.percent = msg.value;
break;
case "status":
this.set("status", msg.value);
this.status = msg.value;
if (msg.value === "complete") {
this.get("model")
.filter((repo) => repo.get("upgrading"))
this.model
.filter((repo) => repo.upgrading)
.forEach((repo) => {
repo.set("version", repo.get("latest.version"));
repo.version = repo.latest?.version;
});
}
@ -70,73 +76,68 @@ export default Controller.extend({
break;
}
},
upgradeButtonText: computed("upgrading", function () {
if (this.get("upgrading")) {
return "Upgrading...";
} else {
return "Start Upgrading";
}
}),
}
startBus() {
this.messageBus.subscribe("/docker/upgrade", (msg) => {
this.messageReceived(msg);
});
},
}
stopBus() {
this.messageBus.unsubscribe("/docker/upgrade");
},
}
reset() {
this.setProperties({ output: "", status: null, percent: 0 });
},
this.output = "";
this.status = null;
this.percent = 0;
}
actions: {
start() {
this.reset();
@action
start() {
this.reset();
if (this.get("multiUpgrade")) {
this.get("model")
.filter((repo) => !repo.get("upToDate"))
.forEach((repo) => repo.set("upgrading", true));
return Repo.upgradeAll();
}
if (this.multiUpgrade) {
this.model
.filter((repo) => !repo.upToDate)
.forEach((repo) => (repo.upgrading = true));
const repo = this.get("model")[0];
if (repo.get("upgrading")) {
return;
}
repo.startUpgrade();
},
return Repo.upgradeAll();
}
resetUpgrade() {
const message = `
WARNING: You should only reset upgrades that have failed and are not running.
const repo = this.model[0];
if (repo.upgrading) {
return;
}
This will NOT cancel currently running builds and should only be used as a last resort.
`;
repo.startUpgrade();
}
this.dialog.confirm({
message,
didConfirm: () => {
if (this.get("multiUpgrade")) {
return Repo.resetAll(
this.get("model").filter((repo) => !repo.get("upToDate"))
).finally(() => {
this.reset();
this.updateAttribute("upgrading", false);
});
}
@action
resetUpgrade() {
const message = `
WARNING: You should only reset upgrades that have failed and are not running.
This will NOT cancel currently running builds and should only be used as a last resort.
`;
const repo = this.get("model")[0];
repo.resetUpgrade().then(() => {
this.dialog.confirm({
message,
didConfirm: async () => {
if (this.multiUpgrade) {
try {
await Repo.resetAll(this.model.filter((repo) => !repo.upToDate));
} finally {
this.reset();
});
},
});
},
},
});
this.updateAttribute("upgrading", false);
return;
}
}
const repo = this.model[0];
await repo.resetUpgrade();
this.reset();
},
});
}
}

View File

@ -1,10 +1,13 @@
import { helper as buildHelper } from "@ember/component/helper";
import { isNone } from "@ember/utils";
import { htmlSafe } from "@ember/template";
export default buildHelper(function (params) {
const [commitsBehind, oldSha, newSha, url] = params;
if (!commitsBehind) {
return "";
}
if (parseInt(commitsBehind, 10) === 0) {
return "";
}
@ -13,7 +16,7 @@ export default buildHelper(function (params) {
commitsBehind === 1 ? "" : "s"
}`;
if (isNone(url)) {
if (!url) {
return description;
}

View File

@ -1,162 +1,176 @@
import { default as EmberObject, computed } from "@ember/object";
import { or } from "@ember/object/computed";
import { isNone } from "@ember/utils";
import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax";
import { tracked } from "@glimmer/tracking";
import { TrackedObject } from "@ember-compat/tracked-built-ins";
let loaded = [];
function concatVersions(repos) {
return repos.map((repo) => repo.get("version")).join(", ");
return repos.map((repo) => repo.version).join(", ");
}
const Repo = EmberObject.extend({
unloaded: true,
checking: false,
export default class Repo {
@tracked unloaded = true;
@tracked checking = false;
@tracked lastCheckedAt = null;
@tracked latest = new TrackedObject({});
checkingStatus: or("unloaded", "checking"),
upToDate: computed("upgrading", "version", "latest.version", function () {
return (
!this.get("upgrading") &&
(this.get("version") === this.get("latest.version"))
);
}),
// model attributes
@tracked name = null;
@tracked path = null;
@tracked branch = null;
@tracked official = false;
@tracked fork = false;
@tracked id = null;
@tracked version = null;
@tracked pretty_version = null;
@tracked url = null;
@tracked upgrading = false;
prettyVersion: computed("version", "pretty_version", function () {
return this.get("pretty_version") || this.get("version")?.substring(0, 8);
}),
constructor(attributes) {
for (const [key, value] of Object.entries(attributes)) {
this[key] = value;
}
}
prettyLatestVersion: computed("latest.{version,pretty_version}", function () {
return (
this.get("latest.pretty_version") ||
this.get("latest.version")?.substring(0, 8)
);
}),
get checkingStatus() {
return this.unloaded || this.checking;
}
get upToDate() {
return !this.upgrading && this.version === this.latest?.version;
}
get prettyVersion() {
return this.pretty_version || this.version?.substring(0, 8);
}
get prettyLatestVersion() {
return this.latest?.pretty_version || this.latest?.version?.substring(0, 8);
}
get shouldCheck() {
if (isNone(this.get("version"))) {
if (this.version === null) {
return false;
}
if (this.get("checking")) {
if (this.checking) {
return false;
}
// Only check once every minute
const lastCheckedAt = this.get("lastCheckedAt");
if (lastCheckedAt) {
const ago = new Date().getTime() - lastCheckedAt;
if (this.lastCheckedAt) {
const ago = new Date().getTime() - this.lastCheckedAt;
return ago > 60 * 1000;
}
return true;
},
repoAjax(url, args) {
args = args || {};
args.data = this.getProperties("path", "version", "branch");
return true;
}
repoAjax(url, args = {}) {
args.data = {
path: this.path,
version: this.version,
branch: this.branch,
};
return ajax(url, args);
},
}
findLatest() {
return new Promise((resolve) => {
if (!this.get("shouldCheck")) {
this.set("unloaded", false);
return resolve();
}
async findLatest() {
if (!this.shouldCheck) {
this.unloaded = false;
return;
}
this.set("checking", true);
this.repoAjax("/admin/docker/latest").then((result) => {
this.setProperties({
unloaded: false,
checking: false,
lastCheckedAt: new Date().getTime(),
latest: EmberObject.create(result.latest),
});
resolve();
});
});
},
this.checking = true;
findProgress() {
return this.repoAjax("/admin/docker/progress").then(
(result) => result.progress
);
},
const result = await this.repoAjax("/admin/docker/latest");
resetUpgrade() {
return this.repoAjax("/admin/docker/upgrade", {
this.unloaded = false;
this.checking = false;
this.lastCheckedAt = new Date().getTime();
for (const [key, value] of Object.entries(result.latest)) {
this.latest[key] = value;
}
}
async findProgress() {
const result = await this.repoAjax("/admin/docker/progress");
return result.progress;
}
async resetUpgrade() {
await this.repoAjax("/admin/docker/upgrade", {
dataType: "text",
type: "DELETE",
}).then(() => {
this.set("upgrading", false);
});
},
startUpgrade() {
this.set("upgrading", true);
this.upgrading = false;
}
return this.repoAjax("/admin/docker/upgrade", {
dataType: "text",
type: "POST",
}).catch(() => {
this.set("upgrading", false);
});
},
});
async startUpgrade() {
this.upgrading = true;
Repo.reopenClass({
findAll() {
return new Promise((resolve) => {
if (loaded.length) {
return resolve(loaded);
}
ajax("/admin/docker/repos").then((result) => {
loaded = result.repos.map((r) => Repo.create(r));
resolve(loaded);
try {
await this.repoAjax("/admin/docker/upgrade", {
dataType: "text",
type: "POST",
});
});
},
} catch (e) {
this.upgrading = false;
}
}
}
findUpgrading() {
return this.findAll().then((result) => result.findBy("upgrading", true));
},
Repo.findAll = async function () {
if (loaded.length) {
return loaded;
}
find(id) {
return this.findAll().then((result) => result.findBy("id", id));
},
const result = await ajax("/admin/docker/repos");
loaded = result.repos.map((r) => new Repo(r));
return loaded;
};
upgradeAll() {
return ajax("/admin/docker/upgrade", {
dataType: "text",
type: "POST",
data: { path: "all" },
});
},
Repo.findUpgrading = async function () {
const result = await Repo.findAll();
return result.findBy("upgrading", true);
};
resetAll(repos) {
return ajax("/admin/docker/upgrade", {
dataType: "text",
type: "DELETE",
data: { path: "all", version: concatVersions(repos) },
});
},
Repo.find = async function (id) {
const result = await Repo.findAll();
return result.findBy("id", id);
};
findLatestAll() {
return ajax("/admin/docker/latest", {
dataType: "text",
type: "GET",
data: { path: "all" },
});
},
Repo.upgradeAll = function () {
return ajax("/admin/docker/upgrade", {
dataType: "text",
type: "POST",
data: { path: "all" },
});
};
findAllProgress(repos) {
return ajax("/admin/docker/progress", {
dataType: "text",
type: "GET",
data: { path: "all", version: concatVersions(repos) },
});
},
});
Repo.resetAll = function (repos) {
return ajax("/admin/docker/upgrade", {
dataType: "text",
type: "DELETE",
data: { path: "all", version: concatVersions(repos) },
});
};
export default Repo;
Repo.findLatestAll = function () {
return ajax("/admin/docker/latest", {
dataType: "text",
type: "GET",
data: { path: "all" },
});
};
Repo.findAllProgress = function (repos) {
return ajax("/admin/docker/progress", {
dataType: "text",
type: "GET",
data: { path: "all", version: concatVersions(repos) },
});
};

View File

@ -22,20 +22,17 @@ export default Route.extend({
controller.setProperties({ model, upgrading: null });
model.forEach((repo) => {
if (repo.get("upgrading")) {
if (repo.upgrading) {
controller.set("upgrading", repo);
}
// Special case: Upgrade docker manager first
if (repo.get("id") === "docker_manager") {
if (repo.id === "docker_manager") {
controller.set("managerRepo", repo);
}
// Special case: If the branch is "main" warn user
if (
repo.get("id") === "discourse" &&
repo.get("branch") === "origin/main"
) {
if (repo.id === "discourse" && repo.branch === "origin/main") {
upgradeController.appendBannerHtml(`
<b>WARNING:</b>
Your Discourse is tracking the 'main' branch which may be unstable,

View File

@ -1,6 +1,5 @@
import Repo from "discourse/plugins/docker_manager/discourse/models/repo";
import Route from "@ember/routing/route";
import EmberObject from "@ember/object";
import { Promise } from "rsvp";
export default Route.extend({
@ -15,16 +14,19 @@ export default Route.extend({
if (Array.isArray(model)) {
return Repo.findLatestAll().then((response) => {
JSON.parse(response).repos.forEach((_repo) => {
const repo = model.find((repo) => repo.get("path") === _repo.path);
const repo = model.find((repo) => repo.path === _repo.path);
if (!repo) {
return;
}
delete _repo.path;
repo.set("latest", EmberObject.create(_repo));
for (const [key, value] of Object.entries(_repo)) {
repo.latest[key] = value;
}
});
return Repo.findAllProgress(
model.filter((repo) => !repo.get("upToDate"))
model.filter((repo) => !repo.upToDate)
).then((progress) => {
this.set("progress", JSON.parse(progress).progress);
});

View File

@ -1,33 +1,29 @@
<h3>Upgrade {{title}}</h3>
<h3>Upgrade {{this.title}}</h3>
<ProgressBar @percent={{this.percent}} />
{{#if complete}}
{{#if this.complete}}
<p>Upgrade completed successfully!</p>
{{/if}}
{{#if failed}}
{{else if this.failed}}
<p>Sorry, there was an error upgrading Discourse. Please check the logs below.</p>
{{/if}}
{{#if isUpToDate}}
{{#unless multiUpgrade}}
<p>{{title}} is at the newest version.</p>
{{#if this.isUpToDate}}
{{#unless this.multiUpgrade}}
<p>{{this.title}} is at the newest version.</p>
{{else}}
<p>Everything is up-to-date.</p>
{{/unless}}
{{else}}
<div style="clear: both">
<button {{action "start"}} disabled={{upgrading}} class="btn">
{{upgradeButtonText}}
</button>
<button {{on "click" this.start}} disabled={{this.upgrading}} class="btn">
{{upgradeButtonText}}
</button>
{{#if upgrading}}
<button {{action "resetUpgrade"}} class="btn unlock">
Reset Upgrade
</button>
{{/if}}
</div>
{{#if this.upgrading}}
<button {{on "click" this.resetUpgrade}} class="btn unlock">
Reset Upgrade
</button>
{{/if}}
{{/if}}
<Console @output={{this.output}} @followOutput={{true}} />

View File

@ -4,8 +4,7 @@ import { find, render } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
import Repo from "discourse/plugins/docker_manager/discourse/models/repo";
const repoObject = Repo.create({
unloaded: false,
const repoObject = new Repo({
branch: "origin/main",
id: "discourse",
name: "discourse",
@ -23,8 +22,7 @@ const repoObject = Repo.create({
},
});
const managerRepo = Repo.create({
unloaded: false,
const managerRepo = new Repo({
branch: "origin/main",
id: "docker_manager",
name: "docker_manager",