Complete server manager and load channel list
This commit is contained in:
parent
9c8dc01f18
commit
4ce30be711
3 changed files with 146 additions and 63 deletions
|
@ -7,10 +7,13 @@ body {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input, button {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -23,8 +26,12 @@ h1 img {
|
||||||
}
|
}
|
||||||
|
|
||||||
#login {
|
#login {
|
||||||
|
display: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
#login:target {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
#login table {
|
#login table {
|
||||||
margin: 0 auto 1em;
|
margin: 0 auto 1em;
|
||||||
}
|
}
|
||||||
|
|
23
index.html
23
index.html
|
@ -14,26 +14,35 @@
|
||||||
<script type="text/javascript">document.body.innerHTML = "";</script>
|
<script type="text/javascript">document.body.innerHTML = "";</script>
|
||||||
|
|
||||||
<h1><img src="/assets/feathermost.svg" alt=""/> Feathermost</h1>
|
<h1><img src="/assets/feathermost.svg" alt=""/> Feathermost</h1>
|
||||||
<form id="login">
|
|
||||||
<h2 id="add-a-server">Add a server</h2>
|
<div id="server_selection">
|
||||||
|
<ul id="server_selection_list"></ul>
|
||||||
|
<a href="#login"><button id="server_selection_add">Add a server</button></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="login">
|
||||||
|
<h2>Add a server</h2>
|
||||||
<table>
|
<table>
|
||||||
<tr id="login_server_row">
|
<tr id="login_server_row">
|
||||||
<th>Server</th>
|
<th>Server</th>
|
||||||
<td><input type="text" id="login_server" value="http://localhost:8080" required/></td>
|
<td><input type="text" id="login_server" value="http://localhost:8080" required/></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Username</th>
|
<th>Email or username</th>
|
||||||
<td><input type="text" id="login_username" required/></td>
|
<td><input type="text" id="login_login_id" required/></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Password</th>
|
<th>Password</th>
|
||||||
<td><input type="password" id="login_password" required/></td>
|
<td><input type="password" id="login_password" required/></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<input type="button" id="login_button" value="Log in"/>
|
<button id="login_button">Log in</button>
|
||||||
|
<a href="#"><button id="server_selection_add">Cancel</button></a>
|
||||||
<div id="login_message"></div>
|
<div id="login_message"></div>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
<pre id="user_json"></pre>
|
<ul id="channel_list"></ul>
|
||||||
|
|
||||||
|
<div id="channel_contents"></div>
|
||||||
|
|
||||||
<script type="text/javascript" src="/ajax.js"></script>
|
<script type="text/javascript" src="/ajax.js"></script>
|
||||||
<script type="text/javascript" src="/main.js"></script>
|
<script type="text/javascript" src="/main.js"></script>
|
||||||
|
|
177
main.js
177
main.js
|
@ -10,34 +10,37 @@ function byId(id, nullOk=false) {
|
||||||
|
|
||||||
|
|
||||||
const LOCALSTORAGE_KEY_SERVER = "mattermostServer";
|
const LOCALSTORAGE_KEY_SERVER = "mattermostServer";
|
||||||
const RE_SERVER_ITEM = new RegExp(`^${LOCALSTORAGE_KEY_SERVER}_(.*)\$`, "").compile();
|
const RE_SERVER_ITEM = new RegExp(`^${LOCALSTORAGE_KEY_SERVER}_(.*)\$`, "");
|
||||||
class Storage {
|
const Storage = {
|
||||||
getServers() {
|
getServers() {
|
||||||
let servers = [];
|
let servers = [];
|
||||||
for (var i = 0; i < window.localStorage.length; i++) {
|
for (var i = 0; i < window.localStorage.length; i++) {
|
||||||
const key = window.localStorage.key(i);
|
const key = window.localStorage.key(i);
|
||||||
const matches = key.match(RE_SERVER_ITEM);
|
const matches = key.match(RE_SERVER_ITEM);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
servers.push(matches[1]);
|
const endpoint = matches[1];
|
||||||
|
console.debug(`Found logged in endpoint ${endpoint}`);
|
||||||
|
let stored = JSON.parse(window.localStorage.getItem(Storage._key_for(endpoint)));
|
||||||
|
servers.push({...stored, endpoint});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return servers;
|
return servers;
|
||||||
}
|
},
|
||||||
|
|
||||||
_key_for(endpoint) {
|
_key_for(endpoint) {
|
||||||
return `${LOCALSTORAGE_KEY_SERVER}_${endpoint}`;
|
return `${LOCALSTORAGE_KEY_SERVER}_${endpoint}`;
|
||||||
}
|
},
|
||||||
|
|
||||||
clear(endpoint) {
|
clear(endpoint) {
|
||||||
window.localStorage.removeItem(this._key_for(endpoint));
|
window.localStorage.removeItem(Storage._key_for(endpoint));
|
||||||
}
|
},
|
||||||
|
|
||||||
store(endpoint, login_id, token) {
|
store(endpoint, login_id, token) {
|
||||||
window.localStorage.setItem(this._key_for(endpoint), JSON.stringify({login_id, token}));
|
window.localStorage.setItem(Storage._key_for(endpoint), JSON.stringify({login_id, token}));
|
||||||
}
|
},
|
||||||
|
|
||||||
get(endpoint) {
|
get(endpoint) {
|
||||||
return JSON.parse(window.localStorage.getItem(this._key_for(endpoint)) || "null");
|
return JSON.parse(window.localStorage.getItem(Storage._key_for(endpoint)) || "null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,11 +127,13 @@ class MattermostClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async myTeams() {
|
async myTeams() {
|
||||||
|
const stored = this.storage.get(this.api.id);
|
||||||
const response = await this.api.get("/users/me/teams", stored.token);
|
const response = await this.api.get("/users/me/teams", stored.token);
|
||||||
return response.responseJson;
|
return response.responseJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
async myChannels(team_id) {
|
async myChannels(team_id) {
|
||||||
|
const stored = this.storage.get(this.api.id);
|
||||||
const response = await this.api.get(`/users/me/teams/${team_id}/channels`, stored.token);
|
const response = await this.api.get(`/users/me/teams/${team_id}/channels`, stored.token);
|
||||||
return response.responseJson;
|
return response.responseJson;
|
||||||
}
|
}
|
||||||
|
@ -148,82 +153,144 @@ function normalizedEndpoint(endpoint) {
|
||||||
|
|
||||||
return `${protocol}${domain}${path}`;
|
return `${protocol}${domain}${path}`;
|
||||||
}
|
}
|
||||||
|
function humanReadableEndpoint(endpoint) {
|
||||||
|
let matches = endpoint.match(/^(https?:\/\/.+)\/api\/v4$/i);
|
||||||
|
if (!matches) throw Error("Invalid endpoint URL");
|
||||||
|
|
||||||
|
return matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
function createClient(endpoint) {
|
function createClient(endpoint) {
|
||||||
const api = new MattermostApi(normalizedEndpoint(endpoint));
|
const api = new MattermostApi(normalizedEndpoint(endpoint));
|
||||||
const storage = new Storage();
|
return new MattermostClient(api, Storage);
|
||||||
return new MattermostClient(api, storage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buttonDisable(element, text) {
|
function buttonDisable(element, text) {
|
||||||
element.value = text;
|
if (!element.dataset.originalText) {
|
||||||
|
element.dataset.originalText = element.innerText;
|
||||||
|
}
|
||||||
|
element.innerText = text;
|
||||||
element.disabled = true;
|
element.disabled = true;
|
||||||
}
|
}
|
||||||
function buttonEnable(element, text) {
|
function buttonEnable(element) {
|
||||||
element.value = text;
|
element.innerText = element.dataset.originalText;
|
||||||
element.disabled = false;
|
element.disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function populateServerSelectionList() {
|
||||||
|
const servers = Storage.getServers();
|
||||||
|
|
||||||
|
let nodes = [];
|
||||||
|
for (let server of servers) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
const endpoint = humanReadableEndpoint(server.endpoint);
|
||||||
|
li.innerText = `${server.login_id}@${endpoint} `;
|
||||||
|
|
||||||
|
const logoutButton = document.createElement("button");
|
||||||
|
logoutButton.className = "logout";
|
||||||
|
logoutButton.innerText = "Log out";
|
||||||
|
logoutButton.addEventListener("click", e => logOut(endpoint, e.currentTarget));
|
||||||
|
|
||||||
|
li.appendChild(logoutButton);
|
||||||
|
nodes.push(li);
|
||||||
|
}
|
||||||
|
byId("server_selection_list").innerHTML = "";
|
||||||
|
byId("server_selection_list").append(...nodes);
|
||||||
|
}
|
||||||
|
populateServerSelectionList();
|
||||||
|
|
||||||
|
function populateChannelList() {
|
||||||
|
async function addChannelItems(endpoint) {
|
||||||
|
const client = createClient(endpoint);
|
||||||
|
|
||||||
|
const teams = await client.myTeams();
|
||||||
|
console.log(teams);
|
||||||
|
|
||||||
|
for (let team of teams) {
|
||||||
|
let nodes = [];
|
||||||
|
const channels = await client.myChannels(team.id);
|
||||||
|
console.log(channels);
|
||||||
|
for (let channel of channels) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = "javascript:void(0)";
|
||||||
|
switch (channel.type) {
|
||||||
|
case "O": // Public channel
|
||||||
|
a.innerText = `${team.name}/${channel.name}`;
|
||||||
|
break;
|
||||||
|
case "P": // Private channel
|
||||||
|
a.innerText = `🔒 ${team.name}/${channel.name}`;
|
||||||
|
break;
|
||||||
|
case "D": // Direct message
|
||||||
|
a.innerText = `👤 ...`;
|
||||||
|
break;
|
||||||
|
case "G": // Group chat
|
||||||
|
a.innerText = `👥 ${channel.display_name}`;
|
||||||
|
break;
|
||||||
|
default: // Unsupported
|
||||||
|
a.innerText = `${channel.type} ${team.name}/${channel.name}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
a.addEventListener("click", () => switchToChannel(team.id, channel.id));
|
||||||
|
li.appendChild(a);
|
||||||
|
nodes.push(li);
|
||||||
|
}
|
||||||
|
byId("channel_list").append(...nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const servers = Storage.getServers();
|
||||||
|
|
||||||
|
byId("channel_list").innerHTML = "";
|
||||||
|
for (let server of servers) {
|
||||||
|
addChannelItems(server.endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
populateChannelList();
|
||||||
|
|
||||||
function logIn() {
|
function logIn() {
|
||||||
byId("user_json").innerText = "";
|
const client = createClient(byId("login_server").value);
|
||||||
|
|
||||||
const client = createClient(byId("server").value);
|
buttonDisable(byId("login_button"), "Logging in...");
|
||||||
|
|
||||||
buttonDisable(byId("login"), "Logging in...");
|
client.logIn(byId("login_login_id").value, byId("login_password").value)
|
||||||
|
|
||||||
client.logIn(byId("username").value, byId("password").value)
|
|
||||||
.then(json => {
|
.then(json => {
|
||||||
buttonEnable(byId("login"), "Logged in");
|
buttonEnable(byId("login_button"));
|
||||||
byId("login_message").innerText = "";
|
byId("login_message").innerText = "";
|
||||||
byId("user_json").innerText = JSON.stringify(json, null, 2);
|
byId("channel_list").innerText = `Logged in as ${json.username}`;
|
||||||
|
window.location = "#";
|
||||||
|
populateServerSelectionList();
|
||||||
|
populateChannelList();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
buttonEnable(byId("login"), "Could not log in");
|
buttonEnable(byId("login_button"));
|
||||||
byId("login_message").innerText = `${error}`;
|
byId("login_message").innerText = `${error}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function logOut() {
|
function logOut(endpoint, button) {
|
||||||
const client = createClient(byId("server").value);
|
const client = createClient(endpoint);
|
||||||
|
|
||||||
buttonDisable(byId("logout"), "Logging out...");
|
buttonDisable(button, "Logging out...");
|
||||||
|
|
||||||
client.logOut()
|
client.logOut()
|
||||||
.then(response => {
|
.then(response => {
|
||||||
buttonEnable(byId("logout"), "Logged out");
|
console.info("Succesfully logged out");
|
||||||
byId("logout_message").innerText = "";
|
populateServerSelectionList();
|
||||||
byId("user_json").innerText = "";
|
populateChannelList();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
buttonEnable(byId("logout"), "Could not log out");
|
const span = document.createElement("span");
|
||||||
byId("logout_message").innerText = `${error}`;
|
span.innerText = `Failed to log out: ${error.message}`;
|
||||||
|
button.parentElement.appendChild(span);
|
||||||
|
buttonEnable(button);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateToken() {
|
function switchToChannel(team_id, channel_id) {
|
||||||
byId("user_json").innerText = "";
|
window.location = "#channel_contents";
|
||||||
|
|
||||||
const client = createClient(byId("server").value);
|
|
||||||
|
|
||||||
buttonDisable(byId("validate"), "Validating token...");
|
|
||||||
|
|
||||||
let cred = client.storage.get(client.api.id);
|
|
||||||
if (!cred || !cred.token) {
|
|
||||||
buttonEnable(byId("validate"), "No token, log in first");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.usersMe()
|
|
||||||
.then(json => {
|
|
||||||
buttonEnable(byId("validate"), "Validation succeeded");
|
|
||||||
byId("validate_message").innerText = "";
|
|
||||||
byId("user_json").innerText = JSON.stringify(json, null, 2);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
buttonEnable(byId("validate"), "Validation failed");
|
|
||||||
byId("validate_message").innerText = `${error}`;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byId("login_button").addEventListener("click", logIn);
|
||||||
|
|
Loading…
Reference in a new issue