This commit is contained in:
syuilo 2018-03-15 19:53:46 +09:00
parent 366a7eafcf
commit 0c481a3f9e
22 changed files with 446 additions and 101 deletions

View File

@ -1,9 +1,11 @@
import Vue from 'vue'; import Vue from 'vue';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import * as merge from 'object-assign-deep'; import * as merge from 'object-assign-deep';
import * as uuid from 'uuid';
import { host, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config'; import { host, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config';
import Progress from './scripts/loading'; import Progress from './scripts/loading';
import Connection from './scripts/streaming/stream';
import { HomeStreamManager } from './scripts/streaming/home'; import { HomeStreamManager } from './scripts/streaming/home';
import { DriveStreamManager } from './scripts/streaming/drive'; import { DriveStreamManager } from './scripts/streaming/drive';
import { ServerStreamManager } from './scripts/streaming/server'; import { ServerStreamManager } from './scripts/streaming/server';
@ -151,9 +153,6 @@ export default class MiOS extends EventEmitter {
this.shouldRegisterSw = shouldRegisterSw; this.shouldRegisterSw = shouldRegisterSw;
this.streams.serverStream = new ServerStreamManager();
this.streams.requestsStream = new RequestsStreamManager();
//#region BIND //#region BIND
this.log = this.log.bind(this); this.log = this.log.bind(this);
this.logInfo = this.logInfo.bind(this); this.logInfo = this.logInfo.bind(this);
@ -165,16 +164,6 @@ export default class MiOS extends EventEmitter {
this.registerSw = this.registerSw.bind(this); this.registerSw = this.registerSw.bind(this);
//#endregion //#endregion
this.once('signedin', () => {
// Init home stream manager
this.stream = new HomeStreamManager(this, this.i);
// Init other stream manager
this.streams.driveStream = new DriveStreamManager(this.i);
this.streams.messagingIndexStream = new MessagingIndexStreamManager(this.i);
this.streams.othelloStream = new OthelloStreamManager(this.i);
});
if (this.debug) { if (this.debug) {
(window as any).os = this; (window as any).os = this;
} }
@ -240,6 +229,21 @@ export default class MiOS extends EventEmitter {
* @param callback A function that call when initialized * @param callback A function that call when initialized
*/ */
public async init(callback) { public async init(callback) {
//#region Init stream managers
this.streams.serverStream = new ServerStreamManager(this);
this.streams.requestsStream = new RequestsStreamManager(this);
this.once('signedin', () => {
// Init home stream manager
this.stream = new HomeStreamManager(this, this.i);
// Init other stream manager
this.streams.driveStream = new DriveStreamManager(this, this.i);
this.streams.messagingIndexStream = new MessagingIndexStreamManager(this, this.i);
this.streams.othelloStream = new OthelloStreamManager(this, this.i);
});
//#endregion
// ユーザーをフェッチしてコールバックする // ユーザーをフェッチしてコールバックする
const fetchme = (token, cb) => { const fetchme = (token, cb) => {
let me = null; let me = null;
@ -414,6 +418,8 @@ export default class MiOS extends EventEmitter {
}); });
} }
public requests = [];
/** /**
* Misskey APIにリクエストします * Misskey APIにリクエストします
* @param endpoint * @param endpoint
@ -446,22 +452,41 @@ export default class MiOS extends EventEmitter {
data data
}); });
} else {*/ } else {*/
const req = {
id: uuid(),
date: new Date(),
name: endpoint,
data,
res: null,
status: null
};
if (this.debug) {
this.requests.push(req);
}
// Send request // Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, { fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
method: 'POST', method: 'POST',
body: JSON.stringify(data), body: JSON.stringify(data),
credentials: endpoint === 'signin' ? 'include' : 'omit', credentials: endpoint === 'signin' ? 'include' : 'omit',
cache: 'no-cache' cache: 'no-cache'
}).then(res => { }).then(async (res) => {
if (--pending === 0) spinner.parentNode.removeChild(spinner); if (--pending === 0) spinner.parentNode.removeChild(spinner);
const body = await res.json();
if (this.debug) {
req.status = res.status;
req.res = body;
}
if (res.status === 200) { if (res.status === 200) {
res.json().then(resolve); resolve(body);
} else if (res.status === 204) { } else if (res.status === 204) {
resolve(); resolve();
} else { } else {
res.json().then(err => { reject(body.error);
reject(err.error);
}, reject);
} }
}).catch(reject); }).catch(reject);
/*}*/ /*}*/
@ -499,17 +524,29 @@ export default class MiOS extends EventEmitter {
} }
}); });
} }
public connections: Connection[] = [];
public registerStreamConnection(connection: Connection) {
this.connections.push(connection);
}
public unregisterStreamConnection(connection: Connection) {
this.connections = this.connections.filter(c => c != connection);
}
} }
class WindowSystem { class WindowSystem extends EventEmitter {
private windows = new Set(); public windows = new Set();
public add(window) { public add(window) {
this.windows.add(window); this.windows.add(window);
this.emit('added', window);
} }
public remove(window) { public remove(window) {
this.windows.delete(window); this.windows.delete(window);
this.emit('removed', window);
} }
public getAll() { public getAll() {

View File

@ -1,11 +1,12 @@
import Stream from './stream'; import Stream from './stream';
import MiOS from '../../mios';
/** /**
* Channel stream connection * Channel stream connection
*/ */
export default class Connection extends Stream { export default class Connection extends Stream {
constructor(channelId) { constructor(os: MiOS, channelId) {
super('channel', { super(os, 'channel', {
channel: channelId channel: channelId
}); });
} }

View File

@ -1,12 +1,13 @@
import Stream from './stream'; import Stream from './stream';
import StreamManager from './stream-manager'; import StreamManager from './stream-manager';
import MiOS from '../../mios';
/** /**
* Drive stream connection * Drive stream connection
*/ */
export class DriveStream extends Stream { export class DriveStream extends Stream {
constructor(me) { constructor(os: MiOS, me) {
super('drive', { super(os, 'drive', {
i: me.token i: me.token
}); });
} }
@ -14,16 +15,18 @@ export class DriveStream extends Stream {
export class DriveStreamManager extends StreamManager<DriveStream> { export class DriveStreamManager extends StreamManager<DriveStream> {
private me; private me;
private os: MiOS;
constructor(me) { constructor(os: MiOS, me) {
super(); super();
this.me = me; this.me = me;
this.os = os;
} }
public getConnection() { public getConnection() {
if (this.connection == null) { if (this.connection == null) {
this.connection = new DriveStream(this.me); this.connection = new DriveStream(this.os, this.me);
} }
return this.connection; return this.connection;

View File

@ -9,7 +9,7 @@ import MiOS from '../../mios';
*/ */
export class HomeStream extends Stream { export class HomeStream extends Stream {
constructor(os: MiOS, me) { constructor(os: MiOS, me) {
super('', { super(os, '', {
i: me.token i: me.token
}); });

View File

@ -1,12 +1,13 @@
import Stream from './stream'; import Stream from './stream';
import StreamManager from './stream-manager'; import StreamManager from './stream-manager';
import MiOS from '../../mios';
/** /**
* Messaging index stream connection * Messaging index stream connection
*/ */
export class MessagingIndexStream extends Stream { export class MessagingIndexStream extends Stream {
constructor(me) { constructor(os: MiOS, me) {
super('messaging-index', { super(os, 'messaging-index', {
i: me.token i: me.token
}); });
} }
@ -14,16 +15,18 @@ export class MessagingIndexStream extends Stream {
export class MessagingIndexStreamManager extends StreamManager<MessagingIndexStream> { export class MessagingIndexStreamManager extends StreamManager<MessagingIndexStream> {
private me; private me;
private os: MiOS;
constructor(me) { constructor(os: MiOS, me) {
super(); super();
this.me = me; this.me = me;
this.os = os;
} }
public getConnection() { public getConnection() {
if (this.connection == null) { if (this.connection == null) {
this.connection = new MessagingIndexStream(this.me); this.connection = new MessagingIndexStream(this.os, this.me);
} }
return this.connection; return this.connection;

View File

@ -1,11 +1,12 @@
import Stream from './stream'; import Stream from './stream';
import MiOS from '../../mios';
/** /**
* Messaging stream connection * Messaging stream connection
*/ */
export class MessagingStream extends Stream { export class MessagingStream extends Stream {
constructor(me, otherparty) { constructor(os: MiOS, me, otherparty) {
super('messaging', { super(os, 'messaging', {
i: me.token, i: me.token,
otherparty otherparty
}); });

View File

@ -1,8 +1,9 @@
import Stream from './stream'; import Stream from './stream';
import MiOS from '../../mios';
export class OthelloGameStream extends Stream { export class OthelloGameStream extends Stream {
constructor(me, game) { constructor(os: MiOS, me, game) {
super('othello-game', { super(os, 'othello-game', {
i: me ? me.token : null, i: me ? me.token : null,
game: game.id game: game.id
}); });

View File

@ -1,9 +1,10 @@
import StreamManager from './stream-manager'; import StreamManager from './stream-manager';
import Stream from './stream'; import Stream from './stream';
import MiOS from '../../mios';
export class OthelloStream extends Stream { export class OthelloStream extends Stream {
constructor(me) { constructor(os: MiOS, me) {
super('othello', { super(os, 'othello', {
i: me.token i: me.token
}); });
} }
@ -11,16 +12,18 @@ export class OthelloStream extends Stream {
export class OthelloStreamManager extends StreamManager<OthelloStream> { export class OthelloStreamManager extends StreamManager<OthelloStream> {
private me; private me;
private os: MiOS;
constructor(me) { constructor(os: MiOS, me) {
super(); super();
this.me = me; this.me = me;
this.os = os;
} }
public getConnection() { public getConnection() {
if (this.connection == null) { if (this.connection == null) {
this.connection = new OthelloStream(this.me); this.connection = new OthelloStream(this.os, this.me);
} }
return this.connection; return this.connection;

View File

@ -1,19 +1,28 @@
import Stream from './stream'; import Stream from './stream';
import StreamManager from './stream-manager'; import StreamManager from './stream-manager';
import MiOS from '../../mios';
/** /**
* Requests stream connection * Requests stream connection
*/ */
export class RequestsStream extends Stream { export class RequestsStream extends Stream {
constructor() { constructor(os: MiOS) {
super('requests'); super(os, 'requests');
} }
} }
export class RequestsStreamManager extends StreamManager<RequestsStream> { export class RequestsStreamManager extends StreamManager<RequestsStream> {
private os: MiOS;
constructor(os: MiOS) {
super();
this.os = os;
}
public getConnection() { public getConnection() {
if (this.connection == null) { if (this.connection == null) {
this.connection = new RequestsStream(); this.connection = new RequestsStream(this.os);
} }
return this.connection; return this.connection;

View File

@ -1,19 +1,28 @@
import Stream from './stream'; import Stream from './stream';
import StreamManager from './stream-manager'; import StreamManager from './stream-manager';
import MiOS from '../../mios';
/** /**
* Server stream connection * Server stream connection
*/ */
export class ServerStream extends Stream { export class ServerStream extends Stream {
constructor() { constructor(os: MiOS) {
super('server'); super(os, 'server');
} }
} }
export class ServerStreamManager extends StreamManager<ServerStream> { export class ServerStreamManager extends StreamManager<ServerStream> {
private os: MiOS;
constructor(os: MiOS) {
super();
this.os = os;
}
public getConnection() { public getConnection() {
if (this.connection == null) { if (this.connection == null) {
this.connection = new ServerStream(); this.connection = new ServerStream(this.os);
} }
return this.connection; return this.connection;

View File

@ -31,6 +31,8 @@ export default abstract class StreamManager<T extends Connection> extends EventE
this._connection.on('_disconnected_', () => { this._connection.on('_disconnected_', () => {
this.emit('_disconnected_'); this.emit('_disconnected_');
}); });
this._connection.user = 'Managed';
} }
} }
@ -77,6 +79,8 @@ export default abstract class StreamManager<T extends Connection> extends EventE
this.users.push(userId); this.users.push(userId);
this._connection.user = `Managed (${ this.users.length })`;
return userId; return userId;
} }
@ -87,6 +91,8 @@ export default abstract class StreamManager<T extends Connection> extends EventE
public dispose(userId) { public dispose(userId) {
this.users = this.users.filter(id => id != userId); this.users = this.users.filter(id => id != userId);
this._connection.user = `Managed (${ this.users.length })`;
// 誰もコネクションの利用者がいなくなったら // 誰もコネクションの利用者がいなくなったら
if (this.users.length == 0) { if (this.users.length == 0) {
// また直ぐに再利用される可能性があるので、一定時間待ち、 // また直ぐに再利用される可能性があるので、一定時間待ち、

View File

@ -1,6 +1,8 @@
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import * as uuid from 'uuid';
import * as ReconnectingWebsocket from 'reconnecting-websocket'; import * as ReconnectingWebsocket from 'reconnecting-websocket';
import { apiUrl } from '../../../config'; import { apiUrl } from '../../../config';
import MiOS from '../../mios';
/** /**
* Misskey stream connection * Misskey stream connection
@ -8,9 +10,21 @@ import { apiUrl } from '../../../config';
export default class Connection extends EventEmitter { export default class Connection extends EventEmitter {
public state: string; public state: string;
private buffer: any[]; private buffer: any[];
private socket: ReconnectingWebsocket; public socket: ReconnectingWebsocket;
public name: string;
public connectedAt: Date;
public user: string = null;
public in: number = 0;
public out: number = 0;
public inout: Array<{
type: 'in' | 'out',
at: Date,
data: string
}> = [];
public id: string;
private os: MiOS;
constructor(endpoint, params?) { constructor(os: MiOS, endpoint, params?) {
super(); super();
//#region BIND //#region BIND
@ -21,6 +35,9 @@ export default class Connection extends EventEmitter {
this.close = this.close.bind(this); this.close = this.close.bind(this);
//#endregion //#endregion
this.id = uuid();
this.os = os;
this.name = endpoint;
this.state = 'initializing'; this.state = 'initializing';
this.buffer = []; this.buffer = [];
@ -35,6 +52,9 @@ export default class Connection extends EventEmitter {
this.socket.addEventListener('open', this.onOpen); this.socket.addEventListener('open', this.onOpen);
this.socket.addEventListener('close', this.onClose); this.socket.addEventListener('close', this.onClose);
this.socket.addEventListener('message', this.onMessage); this.socket.addEventListener('message', this.onMessage);
// Register this connection for debugging
this.os.registerStreamConnection(this);
} }
/** /**
@ -44,11 +64,18 @@ export default class Connection extends EventEmitter {
this.state = 'connected'; this.state = 'connected';
this.emit('_connected_'); this.emit('_connected_');
this.connectedAt = new Date();
// バッファーを処理 // バッファーを処理
const _buffer = [].concat(this.buffer); // Shallow copy const _buffer = [].concat(this.buffer); // Shallow copy
this.buffer = []; // Clear buffer this.buffer = []; // Clear buffer
_buffer.forEach(message => { _buffer.forEach(data => {
this.send(message); // Resend each buffered messages this.send(data); // Resend each buffered messages
if (this.os.debug) {
this.out++;
this.inout.push({ type: 'out', at: new Date(), data });
}
}); });
} }
@ -64,6 +91,11 @@ export default class Connection extends EventEmitter {
* Callback of when received a message from connection * Callback of when received a message from connection
*/ */
private onMessage(message) { private onMessage(message) {
if (this.os.debug) {
this.in++;
this.inout.push({ type: 'in', at: new Date(), data: message.data });
}
try { try {
const msg = JSON.parse(message.data); const msg = JSON.parse(message.data);
if (msg.type) this.emit(msg.type, msg.body); if (msg.type) this.emit(msg.type, msg.body);
@ -75,20 +107,26 @@ export default class Connection extends EventEmitter {
/** /**
* Send a message to connection * Send a message to connection
*/ */
public send(message) { public send(data) {
// まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する // まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
if (this.state != 'connected') { if (this.state != 'connected') {
this.buffer.push(message); this.buffer.push(data);
return; return;
} }
this.socket.send(JSON.stringify(message)); if (this.os.debug) {
this.out++;
this.inout.push({ type: 'out', at: new Date(), data });
}
this.socket.send(JSON.stringify(data));
} }
/** /**
* Close this connection * Close this connection
*/ */
public close() { public close() {
this.os.unregisterStreamConnection(this);
this.socket.removeEventListener('open', this.onOpen); this.socket.removeEventListener('open', this.onOpen);
this.socket.removeEventListener('message', this.onMessage); this.socket.removeEventListener('message', this.onMessage);
} }

View File

@ -10,6 +10,7 @@ import pollEditor from './poll-editor.vue';
import reactionIcon from './reaction-icon.vue'; import reactionIcon from './reaction-icon.vue';
import reactionsViewer from './reactions-viewer.vue'; import reactionsViewer from './reactions-viewer.vue';
import time from './time.vue'; import time from './time.vue';
import timer from './timer.vue';
import images from './images.vue'; import images from './images.vue';
import uploader from './uploader.vue'; import uploader from './uploader.vue';
import specialMessage from './special-message.vue'; import specialMessage from './special-message.vue';
@ -33,6 +34,7 @@ Vue.component('mk-poll-editor', pollEditor);
Vue.component('mk-reaction-icon', reactionIcon); Vue.component('mk-reaction-icon', reactionIcon);
Vue.component('mk-reactions-viewer', reactionsViewer); Vue.component('mk-reactions-viewer', reactionsViewer);
Vue.component('mk-time', time); Vue.component('mk-time', time);
Vue.component('mk-timer', timer);
Vue.component('mk-images', images); Vue.component('mk-images', images);
Vue.component('mk-uploader', uploader); Vue.component('mk-uploader', uploader);
Vue.component('mk-special-message', specialMessage); Vue.component('mk-special-message', specialMessage);

View File

@ -66,7 +66,7 @@ export default Vue.extend({
}, },
mounted() { mounted() {
this.connection = new MessagingStream((this as any).os.i, this.user.id); this.connection = new MessagingStream((this as any).os, (this as any).os.i, this.user.id);
this.connection.on('message', this.onMessage); this.connection.on('message', this.onMessage);
this.connection.on('read', this.onRead); this.connection.on('read', this.onRead);

View File

@ -25,7 +25,7 @@ export default Vue.extend({
}, },
created() { created() {
this.g = this.game; this.g = this.game;
this.connection = new OthelloGameStream((this as any).os.i, this.game); this.connection = new OthelloGameStream((this as any).os, (this as any).os.i, this.game);
this.connection.on('started', this.onStarted); this.connection.on('started', this.onStarted);
}, },
beforeDestroy() { beforeDestroy() {

View File

@ -135,44 +135,6 @@ export default Vue.extend({
if (this.game.user1_id != (this as any).os.i.id && this.game.settings.form1) this.form = this.game.settings.form1; if (this.game.user1_id != (this as any).os.i.id && this.game.settings.form1) this.form = this.game.settings.form1;
if (this.game.user2_id != (this as any).os.i.id && this.game.settings.form2) this.form = this.game.settings.form2; if (this.game.user2_id != (this as any).os.i.id && this.game.settings.form2) this.form = this.game.settings.form2;
// for debugging
if ((this as any).os.i.username == 'test1') {
setTimeout(() => {
this.connection.send({
type: 'init-form',
body: [{
id: 'button1',
type: 'button',
label: 'Enable hoge',
value: false
}, {
id: 'radio1',
type: 'radio',
label: '強さ',
value: 2,
items: [{
label: '弱',
value: 1
}, {
label: '中',
value: 2
}, {
label: '強',
value: 3
}]
}]
});
this.connection.send({
type: 'message',
body: {
text: 'Hey',
type: 'info'
}
});
}, 2000);
}
}, },
beforeDestroy() { beforeDestroy() {

View File

@ -0,0 +1,49 @@
<template>
<time class="mk-time">
{{ hh }}:{{ mm }}:{{ ss }}
</time>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
time: {
type: [Date, String],
required: true
}
},
data() {
return {
tickId: null,
hh: null,
mm: null,
ss: null
};
},
computed: {
_time(): Date {
return typeof this.time == 'string' ? new Date(this.time) : this.time;
}
},
created() {
this.tick();
this.tickId = setInterval(this.tick, 1000);
},
destroyed() {
clearInterval(this.tickId);
},
methods: {
tick() {
const now = new Date().getTime();
const start = this._time.getTime();
const ago = Math.floor((now - start) / 1000);
this.hh = Math.floor(ago / (60 * 60)).toString().padStart(2, '0');
this.mm = Math.floor(ago / 60).toString().padStart(2, '0');
this.ss = (ago % 60).toString().padStart(2, '0');
}
}
});
</script>

View File

@ -173,6 +173,10 @@
<mk-switch v-model="enableExperimental" text="実験的機能を有効にする"> <mk-switch v-model="enableExperimental" text="実験的機能を有効にする">
<span>実験的機能を有効にするとMisskeyの動作が不安定になる可能性がありますこの設定はブラウザに記憶されます</span> <span>実験的機能を有効にするとMisskeyの動作が不安定になる可能性がありますこの設定はブラウザに記憶されます</span>
</mk-switch> </mk-switch>
<details v-if="debug">
<summary>ツール</summary>
<button class="ui button block" @click="taskmngr">タスクマネージャ</button>
</details>
</section> </section>
<section class="other" v-show="page == 'other'"> <section class="other" v-show="page == 'other'">
@ -196,6 +200,7 @@ import XSignins from './settings.signins.vue';
import XDrive from './settings.drive.vue'; import XDrive from './settings.drive.vue';
import { url, docsUrl, license, lang, version } from '../../../config'; import { url, docsUrl, license, lang, version } from '../../../config';
import checkForUpdate from '../../../common/scripts/check-for-update'; import checkForUpdate from '../../../common/scripts/check-for-update';
import MkTaskManager from './taskmanager.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -226,6 +231,11 @@ export default Vue.extend({
enableExperimental: localStorage.getItem('enableExperimental') == 'true' enableExperimental: localStorage.getItem('enableExperimental') == 'true'
}; };
}, },
computed: {
licenseUrl(): string {
return `${docsUrl}/${lang}/license`;
}
},
watch: { watch: {
autoPopout() { autoPopout() {
localStorage.setItem('autoPopout', this.autoPopout ? 'true' : 'false'); localStorage.setItem('autoPopout', this.autoPopout ? 'true' : 'false');
@ -252,17 +262,15 @@ export default Vue.extend({
localStorage.setItem('enableExperimental', this.enableExperimental ? 'true' : 'false'); localStorage.setItem('enableExperimental', this.enableExperimental ? 'true' : 'false');
} }
}, },
computed: {
licenseUrl(): string {
return `${docsUrl}/${lang}/license`;
}
},
created() { created() {
(this as any).os.getMeta().then(meta => { (this as any).os.getMeta().then(meta => {
this.meta = meta; this.meta = meta;
}); });
}, },
methods: { methods: {
taskmngr() {
(this as any).os.new(MkTaskManager);
},
customizeHome() { customizeHome() {
this.$router.push('/i/customize-home'); this.$router.push('/i/customize-home');
this.$emit('done'); this.$emit('done');

View File

@ -0,0 +1,204 @@
<template>
<mk-window ref="window" width="750px" height="500px" @closed="$destroy" name="TaskManager">
<span slot="header" :class="$style.header">%fa:stethoscope%タスクマネージャ</span>
<el-tabs :class="$style.content">
<el-tab-pane label="Requests">
<el-table
:data="os.requests"
style="width: 100%"
:default-sort="{prop: 'date', order: 'descending'}"
>
<el-table-column type="expand">
<template slot-scope="props">
<pre>{{ props.row.data }}</pre>
<pre>{{ props.row.res }}</pre>
</template>
</el-table-column>
<el-table-column
label="Requested at"
prop="date"
sortable
>
<template slot-scope="scope">
<b style="margin-right: 8px">{{ scope.row.date.getTime() }}</b>
<span>(<mk-time :time="scope.row.date"/>)</span>
</template>
</el-table-column>
<el-table-column
label="Name"
>
<template slot-scope="scope">
<b>{{ scope.row.name }}</b>
</template>
</el-table-column>
<el-table-column
label="Status"
>
<template slot-scope="scope">
<span>{{ scope.row.status || '(pending)' }}</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="Streams">
<el-table
:data="os.connections"
style="width: 100%"
>
<el-table-column
label="Uptime"
>
<template slot-scope="scope">
<mk-timer v-if="scope.row.connectedAt" :time="scope.row.connectedAt"/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column
label="Name"
>
<template slot-scope="scope">
<b>{{ scope.row.name == '' ? '[Home]' : scope.row.name }}</b>
</template>
</el-table-column>
<el-table-column
label="User"
>
<template slot-scope="scope">
<span>{{ scope.row.user || '(anonymous)' }}</span>
</template>
</el-table-column>
<el-table-column
prop="state"
label="State"
/>
<el-table-column
prop="in"
label="In"
/>
<el-table-column
prop="out"
label="Out"
/>
</el-table>
</el-tab-pane>
<el-tab-pane label="Streams (Inspect)">
<el-tabs type="card" style="height:50%">
<el-tab-pane v-for="c in os.connections" :label="c.name == '' ? '[Home]' : c.name" :key="c.id" :name="c.id" ref="connectionsTab">
<el-table
:data="c.inout"
style="width: 100%"
:default-sort="{prop: 'at', order: 'descending'}"
>
<el-table-column type="expand">
<template slot-scope="props">
<pre>{{ props.row.data }}</pre>
</template>
</el-table-column>
<el-table-column
label="Date"
prop="at"
sortable
>
<template slot-scope="scope">
<b style="margin-right: 8px">{{ scope.row.at.getTime() }}</b>
<span>(<mk-time :time="scope.row.at"/>)</span>
</template>
</el-table-column>
<el-table-column
label="Type"
>
<template slot-scope="scope">
<span>{{ getMessageType(scope.row.data) }}</span>
</template>
</el-table-column>
<el-table-column
label="Incoming / Outgoing"
prop="type"
/>
</el-table>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="Windows">
<el-table
:data="Array.from(os.windows.windows)"
style="width: 100%"
>
<el-table-column
label="Name"
>
<template slot-scope="scope">
<b>{{ scope.row.name || '(unknown)' }}</b>
</template>
</el-table-column>
<el-table-column
label="Operations"
>
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="scope.row.close">Close</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</mk-window>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
mounted() {
(this as any).os.windows.on('added', this.onWindowsChanged);
(this as any).os.windows.on('removed', this.onWindowsChanged);
},
beforeDestroy() {
(this as any).os.windows.off('added', this.onWindowsChanged);
(this as any).os.windows.off('removed', this.onWindowsChanged);
},
methods: {
getMessageType(data): string {
return data.type ? data.type : '-';
},
onWindowsChanged() {
this.$forceUpdate();
}
}
});
</script>
<style lang="stylus" module>
.header
> [data-fa]
margin-right 4px
.content
height 100%
overflow auto
</style>
<style>
.el-tabs__header {
margin-bottom: 0 !important;
}
.el-tabs__item {
padding: 0 20px !important;
}
</style>

View File

@ -68,7 +68,12 @@ export default Vue.extend({
default: 'auto' default: 'auto'
}, },
popoutUrl: { popoutUrl: {
type: [String, Function] type: [String, Function],
default: null
},
name: {
type: String,
default: null
} }
}, },

View File

@ -54,7 +54,7 @@ export default Vue.extend({
}); });
this.disconnect(); this.disconnect();
this.connection = new ChannelStream(this.channel.id); this.connection = new ChannelStream((this as any).os, this.channel.id);
this.connection.on('post', this.onPost); this.connection.on('post', this.onPost);
}); });
}, },

View File

@ -243,8 +243,12 @@ module.exports = entries.map(x => {
cache: true, cache: true,
devtool: 'source-map', devtool: 'source-map',
optimization: { optimization: {
minimize: doMinify minimize: isProduction && doMinify
}, },
mode: doMinify ? 'production' : 'development' mode: isProduction
? doMinify
? 'production'
: 'development'
: 'development'
}; };
}); });