mirror of
https://github.com/thelounge/thelounge
synced 2024-11-27 06:20:26 +00:00
Merge pull request #1536 from thelounge/xpaw/session-list
Implement session list and allow signing out other clients
This commit is contained in:
commit
3fc0f4e686
10 changed files with 131 additions and 25 deletions
|
@ -252,6 +252,14 @@ kbd {
|
|||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.session-list strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.session-list p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#chat .invite .from::before {
|
||||
content: "\f003"; /* http://fontawesome.io/icon/envelope-o/ */
|
||||
color: #2ecc40;
|
||||
|
@ -668,6 +676,7 @@ kbd {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
#windows p,
|
||||
#windows label,
|
||||
#settings .error {
|
||||
font-size: 14px;
|
||||
|
|
|
@ -198,15 +198,14 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="settings" class="window">
|
||||
<div id="settings" class="window" data-type="settings">
|
||||
<div class="header">
|
||||
<button class="lt" aria-label="Toggle channel list"></button>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1 class="title">Settings</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1 class="title">Settings</h1>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<h2>Messages</h2>
|
||||
</div>
|
||||
|
@ -380,6 +379,18 @@
|
|||
<textarea class="input" name="userStyles" id="user-specified-css-input" placeholder="You can override any style with CSS here"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#unless public}}
|
||||
<div class="session-list">
|
||||
<h2>Sessions</h2>
|
||||
|
||||
<h3>Current session</h3>
|
||||
<div id="session-current"></div>
|
||||
|
||||
<h3>Other sessions</h3>
|
||||
<div id="session-list"></div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -383,8 +383,9 @@ $(function() {
|
|||
}
|
||||
document.title = title;
|
||||
|
||||
const type = chan.data("type");
|
||||
var placeholder = "";
|
||||
if (chan.data("type") === "channel" || chan.data("type") === "query") {
|
||||
if (type === "channel" || type === "query") {
|
||||
placeholder = `Write to ${chan.data("title")}`;
|
||||
}
|
||||
input.attr("placeholder", placeholder);
|
||||
|
@ -404,6 +405,11 @@ $(function() {
|
|||
socket.emit("names", {target: self.data("id")});
|
||||
}
|
||||
|
||||
if (type === "settings") {
|
||||
$("#session-list").html("<p>Loading…</p>");
|
||||
socket.emit("sessions:get");
|
||||
}
|
||||
|
||||
focus();
|
||||
});
|
||||
|
||||
|
|
|
@ -17,3 +17,4 @@ require("./sync_sort");
|
|||
require("./topic");
|
||||
require("./users");
|
||||
require("./sign_out");
|
||||
require("./sessions_list");
|
||||
|
|
31
client/js/socket-events/sessions_list.js
Normal file
31
client/js/socket-events/sessions_list.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
"use strict";
|
||||
|
||||
const $ = require("jquery");
|
||||
const socket = require("../socket");
|
||||
const templates = require("../../views");
|
||||
|
||||
socket.on("sessions:list", function(data) {
|
||||
data.sort((a, b) => b.lastUse - a.lastUse);
|
||||
|
||||
let html = "";
|
||||
data.forEach((connection) => {
|
||||
if (connection.current) {
|
||||
$("#session-current").html(templates.session(connection));
|
||||
return;
|
||||
}
|
||||
|
||||
html += templates.session(connection);
|
||||
});
|
||||
|
||||
if (html.length === 0) {
|
||||
html = "<p><em>You are not currently logged in to any other device.</em></p>";
|
||||
}
|
||||
|
||||
$("#session-list").html(html);
|
||||
});
|
||||
|
||||
$("#settings").on("click", ".remove-session", function() {
|
||||
socket.emit("sign-out", $(this).data("token"));
|
||||
|
||||
return false;
|
||||
});
|
|
@ -34,6 +34,7 @@ module.exports = {
|
|||
msg_unhandled: require("./msg_unhandled.tpl"),
|
||||
network: require("./network.tpl"),
|
||||
image_viewer: require("./image_viewer.tpl"),
|
||||
session: require("./session.tpl"),
|
||||
unread_marker: require("./unread_marker.tpl"),
|
||||
user: require("./user.tpl"),
|
||||
user_filtered: require("./user_filtered.tpl"),
|
||||
|
|
17
client/views/session.tpl
Normal file
17
client/views/session.tpl
Normal file
|
@ -0,0 +1,17 @@
|
|||
<p>
|
||||
{{#if current}}
|
||||
<strong>{{agent}}</strong>
|
||||
<a href="https://ipinfo.io/{{ip}}" target="_blank" rel="noopener">{{ip}}</a>
|
||||
{{else}}
|
||||
<button class="btn pull-right remove-session" data-token="{{token}}">Disconnect</button>
|
||||
|
||||
<strong>{{agent}}</strong>
|
||||
<a href="https://ipinfo.io/{{ip}}" target="_blank" rel="noopener">{{ip}}</a>
|
||||
<br>
|
||||
{{#if active}}
|
||||
<em>Currently active</em>
|
||||
{{else}}
|
||||
Last used on <time>{{localetime lastUse}}</time>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</p>
|
|
@ -304,11 +304,15 @@ Client.prototype.updateSession = function(token, ip, request) {
|
|||
friendlyAgent += ` on ${agent.os.name} ${agent.os.version}`;
|
||||
}
|
||||
|
||||
client.config.sessions[token] = _.assign({
|
||||
client.config.sessions[token] = _.assign(client.config.sessions[token], {
|
||||
lastUse: Date.now(),
|
||||
ip: ip,
|
||||
agent: friendlyAgent,
|
||||
}, client.config.sessions[token]);
|
||||
});
|
||||
|
||||
client.manager.updateUser(client.name, {
|
||||
sessions: client.config.sessions
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.setPassword = function(hash, callback) {
|
||||
|
@ -318,7 +322,6 @@ Client.prototype.setPassword = function(hash, callback) {
|
|||
password: hash
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
log.error("Failed to update password of", client.name, err);
|
||||
return callback(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ ClientManager.prototype.updateUser = function(name, opts, callback) {
|
|||
|
||||
fs.writeFile(Helper.getUserConfigPath(name), newUser, (err) => {
|
||||
if (err) {
|
||||
log.error("Failed to update user", err);
|
||||
log.error(`Failed to update user ${colors.green(name)} (${err})`);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
|
|
|
@ -237,7 +237,6 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||
socket.on("disconnect", function() {
|
||||
client.clientDetach(socket.id);
|
||||
});
|
||||
client.clientAttach(socket.id, token);
|
||||
|
||||
socket.on(
|
||||
"input",
|
||||
|
@ -378,23 +377,59 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||
client.unregisterPushSubscription(token);
|
||||
});
|
||||
|
||||
socket.on("sign-out", () => {
|
||||
delete client.config.sessions[token];
|
||||
const sendSessionList = () => {
|
||||
const sessions = _.map(client.config.sessions, (session, sessionToken) => ({
|
||||
current: sessionToken === token,
|
||||
active: _.find(client.attachedClients, (u) => u.token === sessionToken) !== undefined,
|
||||
lastUse: session.lastUse,
|
||||
ip: session.ip,
|
||||
agent: session.agent,
|
||||
token: sessionToken, // TODO: Ideally don't expose actual tokens to the client
|
||||
}));
|
||||
|
||||
socket.emit("sessions:list", sessions);
|
||||
};
|
||||
|
||||
socket.on("sessions:get", sendSessionList);
|
||||
|
||||
socket.on("sign-out", (tokenToSignOut) => {
|
||||
// If no token provided, sign same client out
|
||||
if (!tokenToSignOut) {
|
||||
tokenToSignOut = token;
|
||||
}
|
||||
|
||||
if (!(tokenToSignOut in client.config.sessions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete client.config.sessions[tokenToSignOut];
|
||||
|
||||
client.manager.updateUser(client.name, {
|
||||
sessions: client.config.sessions
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
log.error("Failed to update sessions for", client.name, err);
|
||||
}
|
||||
});
|
||||
|
||||
socket.emit("sign-out");
|
||||
_.map(client.attachedClients, (attachedClient, socketId) => {
|
||||
if (attachedClient.token !== tokenToSignOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
const socketToRemove = manager.sockets.of("/").connected[socketId];
|
||||
|
||||
socketToRemove.emit("sign-out");
|
||||
socketToRemove.disconnect();
|
||||
});
|
||||
|
||||
// Do not send updated session list if user simply logs out
|
||||
if (tokenToSignOut !== token) {
|
||||
sendSessionList();
|
||||
}
|
||||
});
|
||||
|
||||
socket.join(client.id);
|
||||
|
||||
const sendInitEvent = (tokenToSend) => {
|
||||
client.clientAttach(socket.id, token);
|
||||
|
||||
let networks = client.networks;
|
||||
|
||||
if (lastMessage > -1) {
|
||||
|
@ -423,14 +458,6 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||
|
||||
client.updateSession(token, getClientIp(socket.request), socket.request);
|
||||
|
||||
client.manager.updateUser(client.name, {
|
||||
sessions: client.config.sessions
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
log.error("Failed to update sessions for", client.name, err);
|
||||
}
|
||||
});
|
||||
|
||||
sendInitEvent(token);
|
||||
});
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue