@browsercapturesalt/herokuappman
Version:
Heroku application manager.
476 lines (460 loc) • 13.4 kB
HTML
<style>
.accounts {
border-collapse: collapse;
font-family: monospace;
font-size: 20px;
}
.accounts td {
border: solid 1px #777;
padding: 3px;
padding-left: 10px;
padding-right: 10px;
}
.builds {
border-collapse: collapse;
font-family: monospace;
font-size: 20px;
}
.builds td {
border: solid 1px #777;
padding: 3px;
padding-left: 10px;
padding-right: 10px;
}
.config {
border-collapse: collapse;
font-family: monospace;
font-size: 20px;
}
.config td {
border: solid 1px #777;
padding: 3px;
padding-left: 10px;
padding-right: 10px;
}
</style>
<script src="/vue.js"></script>
<script src="/utils.js"></script>
<div id="app">
<div v-if="!params.appName">
{{ welcome }}
<hr />
<button @click="setadminpass">Set Admin Pass</button>
<hr />
<table class="accounts">
<tr v-for="account in appMan.accounts">
<td style="font-weight: bold; color: #700; text-align: center">
{{ account.name }}
</td>
<td style="font-weight: bold; color: #070; text-align: center">
{{ Math.round((account.quotaTotal - account.quotaUsed)/3600) }}
</td>
<td>
<table>
<tr v-for="app in account.apps">
<td
style="
font-weight: bold;
color: #007;
min-width: 200px;
font-size: 18px;
"
>
<a
rel="noopener noreferrer"
target="_blank"
:href="`https://${app.name}.herokuapp.com`"
>{{ app.name }}
</a>
</td>
<td
style="
font-weight: bold;
color: #700;
min-width: 30px;
text-align: center;
font-size: 18px;
"
>
{{ Math.round(app.quotaUsed/3600) }}
</td>
<td
style="
font-weight: bold;
color: #770;
min-width: 30px;
text-align: center;
"
>
{{ app.region }}
</td>
<td style="">
<button
style="background-color: #ffa; margin-left: 10px"
@click="getconfig(app)"
>
Config
</button>
<button
style="background-color: #afa; margin-left: 10px"
@click="getlogs(app)"
>
Logs
</button>
<button
style="background-color: #aff; margin-left: 10px"
@click="getbuilds(app)"
>
Builds
</button>
</td>
</tr>
</table>
</td>
</tr>
</table>
<hr />
<input
v-model="filterrepo"
placeholder="Filter by Repo Name"
style="font-family: monospace; font-size: 16px"
/>
<select
v-model="sortReposBy"
style="
font-family: monospace;
margin-left: 10px;
font-size: 16px;
padding: 3px;
"
>
<option
v-for="sortCriteria in sortReposByCriteria"
:value="sortCriteria.value"
>
{{ sortCriteria.display }}
</option>
</select>
<hr />
<table class="accounts">
<tr v-for="account in gitMan.accounts">
<td style="font-weight: bold; color: #700; text-align: center">
<a rel="noopener noreferrer" target="_blank" :href="account.gitUrl">
{{ account.gitUserName }}
</a>
</td>
<td>
<button
@click="createrepo(account.gitUserName, false)"
style="background-color: #afa"
>
Create Empty Repo
</button>
<button
@click="createrepo(account.gitUserName, true)"
style="margin-left: 10px; background-color: #ffa"
>
Create Initialized Repo
</button>
<hr />
<div style="max-height: 300px; overflow-y: scroll">
<table>
<tr
v-for="(repo, index) in account.repos.filter(repo => repo.name.match(new RegExp(filterrepo))).sort(sortReposFunc)"
>
<td>{{ index + 1 }}</td>
<td>
<a
rel="noopener noreferrer"
target="_blank"
:href="repo.repoUrl"
>
{{ repo.name }}
</a>
</td>
<td style="text-align: center; color: #070; font-weight: bold">
{{ repo.stars ? `* ${repo.stars}` : "" }}
</td>
<td style="text-align: center; color: #707; font-weight: bold">
{{ repo.forks ? `-> ${repo.forks}` : "" }}
</td>
<td style="color: #770">> {{ repo.pushedAt }}</td>
<td>
<select @change="forkat($event, account, repo)">
<option value="">Fork At</option>
<option
v-for="account in gitMan.accounts"
:value="account.gitUserName"
>
{{ account.gitUserName }}
</option>
</select>
</td>
<td>
<button
@click="deleterepo(account.gitUserName, repo)"
style="background-color: #faa"
>
Del
</button>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</div>
<div v-if="params.config">
<span style="font-family: monospace">
Config of
<span style="color: #070; font-weight: bold; font-size: 20px"
>{{ params.appName }}</span
>
</span>
<hr />
<button style="background-color: #afa" @click="addkey">Add Key</button>
<button
style="background-color: #ffa; margin-left: 10px"
@click="uploadconfig"
>
Upload Config
</button>
<hr />
<table class="config">
<tr
v-for="key in Object.keys(params.config).sort((a,b)=>a.localeCompare(b))"
>
<td style="color: #007">{{ key }}</td>
<td>
<button style="background-color: #afa" @click="editkey(key)">
Edit
</button>
</td>
<td style="max-width: 600px; overflow: hidden">
{{params.config[key]}}
</td>
<td>
<button style="background-color: #afa" @click="editvalue(key)">
Edit
</button>
</td>
<td>
<button style="background-color: #faa" @click="delkey(key)">
Del
</button>
</td>
</tr>
</table>
</div>
<div v-if="params.builds">
<span style="font-family: monospace">
Builds of
<span style="color: #070; font-weight: bold; font-size: 20px"
>{{ params.appName }}</span
>
</span>
<hr />
<table class="builds">
<tr v-for="build in params.builds">
<td style="color: #007">{{ build.id }}</td>
<td>{{build.created_at}}</td>
<td
:style="`font-weight:bold;${build.status === 'succeeded'?'color:#070;':'color:#700;'}`"
>
{{ build.status }}
</td>
<td>
<a
rel="noopener noreferrer"
target="_blank"
:href="build.output_stream_url"
>Logs</a
>
</td>
</tr>
</table>
</div>
</div>
<script>
Vue.createApp({
computed: {},
methods: {
sortReposFunc(a, b) {
if (this.sortReposBy === "pushedat")
return (
new Date(b.pushedAt).getTime() - new Date(a.pushedAt).getTime()
);
if (this.sortReposBy === "stars") return b.stars - a.stars;
if (this.sortReposBy === "forks") return b.forks - a.forks;
return a.name.localeCompare(b.name);
},
forkat(ev, account, repo) {
const gitUserName = ev.target.value;
ev.target.value = "";
const owner = account.gitUserName;
const name = repo.name;
post("fork", {
gitUserName,
owner,
name,
}).then((result) => {
if (result.forkResult.error) {
window.alert(JSON.stringify(result.forkResult));
} else {
this.gitMan = result.gitMan;
}
});
},
createrepo(gitUserName, init) {
const name = window.prompt("Name");
if (name) {
post("createrepo", {
gitUserName,
name,
init,
}).then((result) => {
if (result.createResult.error) {
window.alert(JSON.stringify(result.createResult));
} else {
this.gitMan = result.gitMan;
}
});
}
},
deleterepo(gitUserName, repo) {
const confirm = window.prompt(`Type "${repo.name}" To Delete`);
if (confirm === repo.name) {
post("deleterepo", {
gitUserName,
name: repo.name,
}).then((result) => {
if (result.deleteResult.error) {
window.alert(JSON.stringify(result.deleteResult));
} else {
this.gitMan = result.gitMan;
}
});
} else {
window.alert("Deletion Canceled");
}
},
uploadconfig() {
post("setconfig", {
name: this.params.appName,
config: this.params.config,
}).then((result) => {
if (result.error) {
window.alert(result.error);
} else {
document.location.reload();
}
});
},
addkey() {
const key = window.prompt("Key");
if (key) {
const value = window.prompt("Value");
if (value !== undefined && value !== null) {
const smartKey = key.toUpperCase().replace(/ /g, "_");
this.params.config[smartKey] = value;
}
}
},
editkey(key) {
const value = this.params.config[key];
const newKey = window.prompt("Key", key);
if (newKey) {
if (this.params.config[newKey] !== undefined) {
window.alert("Key already exists!");
} else {
delete this.params.config[key];
this.params.config[newKey] = value;
}
}
},
delkey(key) {
delete this.params.config[key];
},
editvalue(key) {
const value = this.params.config[key];
const newValue = window.prompt("Key", value);
if (newValue) {
this.params.config[key] = newValue;
}
},
getlogs(app) {
post("getlogs", { app }).then((result) => {
if (result.error) {
window.alert(result.error);
} else {
window.open(result.logplex_url, "_blank");
}
});
},
getbuilds(app) {
window.open(`?builds=${app.name}`, "_blank");
},
getconfig(app) {
window.open(`?config=${app.name}`, "_blank");
},
setadminpass() {
const pass = window.prompt("Admin Pass");
localStorage.setItem("ADMIN_PASS", pass);
document.location.reload();
},
},
data() {
return {
welcome: "Welcome to Heroku App Manager !",
appMan: { accounts: [] },
gitMan: { accounts: [] },
params: {},
filterrepo: "",
sortReposBy: "pushedat",
sortReposByCriteria: [
{ value: "pushedat", display: "Sort repos by most recent push" },
{ value: "alphabetical", display: "Sort repos alphabetically" },
{ value: "stars", display: "Sort repos by stars" },
{ value: "forks", display: "Sort repos by forks" },
],
};
},
mounted() {
const params = new URL(document.location).searchParams;
const builds = params.get("builds");
if (builds) {
this.params.builds = [];
this.params.appName = builds;
post("getbuilds", { name: builds }).then((result) => {
if (result.error) {
window.alert(result.error);
} else {
this.params.builds = result;
}
});
}
const config = params.get("config");
if (config) {
this.params.config = {};
this.params.appName = config;
post("getconfig", { name: config }).then((result) => {
if (result.error) {
window.alert(result.error);
} else {
this.params.config = result;
}
});
}
post("allman", {}).then((result) => {
if (result.error) {
window.alert(result.error);
} else {
this.appMan = result.appMan;
this.gitMan = result.gitMan;
}
});
},
}).mount("#app");
hotReload();
</script>