mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-30 11:03:08 +02:00
Merge branch 'develop'
This commit is contained in:
commit
80d8af84dd
23 changed files with 180 additions and 35 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -17,6 +17,17 @@ npm i -g ts-node
|
|||
npm run migrate
|
||||
```
|
||||
|
||||
11.22.0 (2019/06/18)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* 管理画面でデータベースの各テーブルのレコード数やサイズを確認できるように
|
||||
* サーバー情報にPostgreSQLのバージョンを追加
|
||||
|
||||
### 🐛Fixes
|
||||
* リモートファイルのダウンロードに失敗することがある問題を修正
|
||||
* アンケートの期間を日時指定で選択すると日時がUTCになってしまう問題を修正
|
||||
* MFMのパースを修正
|
||||
|
||||
11.21.0 (2019/06/16)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
|
|
|
@ -1226,8 +1226,12 @@ admin/views/index.vue:
|
|||
abuse: "スパム報告"
|
||||
queue: "ジョブキュー"
|
||||
logs: "ログ"
|
||||
db: "データベース"
|
||||
back-to-misskey: "Misskeyに戻る"
|
||||
|
||||
admin/views/db.vue:
|
||||
tables: "テーブル"
|
||||
|
||||
admin/views/dashboard.vue:
|
||||
dashboard: "ダッシュボード"
|
||||
accounts: "アカウント"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.21.0",
|
||||
"version": "11.22.0",
|
||||
"codename": "daybreak",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -214,7 +214,7 @@
|
|||
"style-loader": "0.23.1",
|
||||
"stylus": "0.54.5",
|
||||
"stylus-loader": "3.0.2",
|
||||
"summaly": "2.2.0",
|
||||
"summaly": "2.3.0",
|
||||
"systeminformation": "4.11.1",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"terser-webpack-plugin": "1.3.0",
|
||||
|
|
|
@ -124,7 +124,7 @@ export default Vue.extend({
|
|||
this.connection = this.$root.stream.useSharedConnection('serverStats');
|
||||
|
||||
this.updateStats();
|
||||
this.clock = setInterval(this.updateStats, 1000);
|
||||
this.clock = setInterval(this.updateStats, 3000);
|
||||
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
|
|
39
src/client/app/admin/views/db.vue
Normal file
39
src/client/app/admin/views/db.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div>
|
||||
<ui-card>
|
||||
<template #title><fa :icon="faDatabase"/> {{ $t('tables') }}</template>
|
||||
<section v-if="tables">
|
||||
<div v-for="table in Object.keys(tables)"><b>{{ table }}</b> {{ tables[table].count | number }} {{ tables[table].size | bytes }}</div>
|
||||
</section>
|
||||
</ui-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import { faDatabase } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('admin/views/db.vue'),
|
||||
|
||||
data() {
|
||||
return {
|
||||
tables: null,
|
||||
faDatabase
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.$root.api('admin/get-table-stats').then(tables => {
|
||||
this.tables = tables;
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -22,6 +22,7 @@
|
|||
<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
|
||||
<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li>
|
||||
<li @click="nav('logs')" :class="{ active: page == 'logs' }"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</li>
|
||||
<li @click="nav('db')" :class="{ active: page == 'db' }"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</li>
|
||||
<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
|
||||
<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
|
||||
<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
|
||||
|
@ -43,6 +44,7 @@
|
|||
<div v-if="page == 'instance'"><x-instance/></div>
|
||||
<div v-if="page == 'queue'"><x-queue/></div>
|
||||
<div v-if="page == 'logs'"><x-logs/></div>
|
||||
<div v-if="page == 'db'"><x-db/></div>
|
||||
<div v-if="page == 'moderators'"><x-moderators/></div>
|
||||
<div v-if="page == 'users'"><x-users/></div>
|
||||
<div v-if="page == 'emoji'"><x-emoji/></div>
|
||||
|
@ -59,19 +61,20 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import { version } from '../../config';
|
||||
import XDashboard from "./dashboard.vue";
|
||||
import XInstance from "./instance.vue";
|
||||
import XQueue from "./queue.vue";
|
||||
import XLogs from "./logs.vue";
|
||||
import XModerators from "./moderators.vue";
|
||||
import XEmoji from "./emoji.vue";
|
||||
import XAnnouncements from "./announcements.vue";
|
||||
import XUsers from "./users.vue";
|
||||
import XDrive from "./drive.vue";
|
||||
import XAbuse from "./abuse.vue";
|
||||
import XFederation from "./federation.vue";
|
||||
import XDashboard from './dashboard.vue';
|
||||
import XInstance from './instance.vue';
|
||||
import XQueue from './queue.vue';
|
||||
import XLogs from './logs.vue';
|
||||
import XDb from './db.vue';
|
||||
import XModerators from './moderators.vue';
|
||||
import XEmoji from './emoji.vue';
|
||||
import XAnnouncements from './announcements.vue';
|
||||
import XUsers from './users.vue';
|
||||
import XDrive from './drive.vue';
|
||||
import XAbuse from './abuse.vue';
|
||||
import XFederation from './federation.vue';
|
||||
|
||||
import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream, faDatabase } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faGrin } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
// Detect the user agent
|
||||
|
@ -85,6 +88,7 @@ export default Vue.extend({
|
|||
XInstance,
|
||||
XQueue,
|
||||
XLogs,
|
||||
XDb,
|
||||
XModerators,
|
||||
XEmoji,
|
||||
XAnnouncements,
|
||||
|
@ -108,7 +112,8 @@ export default Vue.extend({
|
|||
faGlobe,
|
||||
faExclamationCircle,
|
||||
faTasks,
|
||||
faStream
|
||||
faStream,
|
||||
faDatabase,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -89,9 +89,7 @@ export default Vue.extend({
|
|||
|
||||
get() {
|
||||
const at = () => {
|
||||
const [date] = moment(this.atDate).toISOString().split('T');
|
||||
const [hour, minute] = this.atTime.split(':');
|
||||
return moment(`${date}T${hour}:${minute}Z`).valueOf();
|
||||
return moment(`${this.atDate} ${this.atTime}`).valueOf();
|
||||
};
|
||||
|
||||
const after = () => {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<p>Maintainer: <b><a :href="'mailto:' + meta.maintainerEmail" target="_blank">{{ meta.maintainerName }}</a></b></p>
|
||||
<p>Machine: {{ meta.machine }}</p>
|
||||
<p>Node: {{ meta.node }}</p>
|
||||
<p>PSQL: {{ meta.psql }}</p>
|
||||
<p>Version: {{ meta.version }} </p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { URL } from 'url';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { Source, Mixin } from './types';
|
||||
import * as pkg from '../../package.json';
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
|
||||
import { URL } from 'url';
|
||||
import { urlRegex } from './prelude';
|
||||
|
||||
export function fromHtml(html: string): string {
|
||||
|
|
|
@ -98,13 +98,13 @@ export const mfmLanguage = P.createLanguage({
|
|||
const text = input.substr(i);
|
||||
const match = text.match(/^(\*|_)([a-zA-Z0-9]+?[\s\S]*?)\1/);
|
||||
if (!match) return P.makeFailure(i, 'not a italic');
|
||||
if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a italic');
|
||||
if (input[i - 1] != null && input[i - 1] != ' ' && input[i - 1] != '\n') return P.makeFailure(i, 'not a italic');
|
||||
return P.makeSuccess(i + match[0].length, match[2]);
|
||||
});
|
||||
|
||||
return P.alt(xml, underscore).map(x => createTree('italic', r.inline.atLeast(1).tryParse(x), {}));
|
||||
},
|
||||
strike: r => P.regexp(/~~(.+?)~~/, 1).map(x => createTree('strike', r.inline.atLeast(1).tryParse(x), {})),
|
||||
strike: r => P.regexp(/~~([^\n~]+?)~~/, 1).map(x => createTree('strike', r.inline.atLeast(1).tryParse(x), {})),
|
||||
motion: r => {
|
||||
const paren = P.regexp(/\(\(\(([\s\S]+?)\)\)\)/, 1);
|
||||
const xml = P.regexp(/<motion>(.+?)<\/motion>/, 1);
|
||||
|
@ -164,8 +164,10 @@ export const mfmLanguage = P.createLanguage({
|
|||
} else
|
||||
url = match[0];
|
||||
url = removeOrphanedBrackets(url);
|
||||
while (url.endsWith('.') || url.endsWith(',')) {
|
||||
if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
|
||||
if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
|
||||
}
|
||||
return P.makeSuccess(i + url.length, url);
|
||||
}).map(x => createLeaf('url', { url: x }));
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import config from '../config';
|
||||
import { toASCII } from 'punycode';
|
||||
import { URL } from 'url';
|
||||
|
||||
export function getFullApAccount(username: string, host: string | null) {
|
||||
return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
|
||||
|
|
|
@ -25,10 +25,8 @@ export async function downloadUrl(url: string, path: string) {
|
|||
rej(error);
|
||||
});
|
||||
|
||||
const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
|
||||
|
||||
const req = request({
|
||||
url: requestUrl,
|
||||
url: new URL(url).href, // https://github.com/syuilo/misskey/issues/2637
|
||||
proxy: config.proxy,
|
||||
timeout: 10 * 1000,
|
||||
headers: {
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as httpSignature from 'http-signature';
|
|||
import { IRemoteUser } from '../../models/entities/user';
|
||||
import perform from '../../remote/activitypub/perform';
|
||||
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
|
||||
import { URL } from 'url';
|
||||
import { publishApLogStream } from '../../services/stream';
|
||||
import Logger from '../../services/logger';
|
||||
import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc';
|
||||
|
|
|
@ -6,7 +6,6 @@ import { resolveImage } from './image';
|
|||
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { fromHtml } from '../../../mfm/fromHtml';
|
||||
import { URL } from 'url';
|
||||
import { resolveNote, extractEmojis } from './note';
|
||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
||||
import { ITag, extractHashtags } from './tag';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { request } from 'https';
|
||||
import { sign } from 'http-signature';
|
||||
import { URL } from 'url';
|
||||
import * as crypto from 'crypto';
|
||||
import { lookup, IRunOptions } from 'lookup-dns-cache';
|
||||
import * as promiseAny from 'promise-any';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import webFinger from './webfinger';
|
||||
import config from '../config';
|
||||
import { createPerson, updatePerson } from './activitypub/models/person';
|
||||
import { URL } from 'url';
|
||||
import { remoteLogger } from './logger';
|
||||
import chalk from 'chalk';
|
||||
import { User, IRemoteUser } from '../models/entities/user';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import config from '../config';
|
||||
import * as request from 'request-promise-native';
|
||||
import { URL } from 'url';
|
||||
import { query as urlQuery } from '../prelude/url';
|
||||
|
||||
type ILink = {
|
||||
|
|
37
src/server/api/endpoints/admin/get-table-stats.ts
Normal file
37
src/server/api/endpoints/admin/get-table-stats.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import define from '../../define';
|
||||
import { getConnection } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: false,
|
||||
|
||||
desc: {
|
||||
'en-US': 'Get table stats'
|
||||
},
|
||||
|
||||
tags: ['meta'],
|
||||
|
||||
params: {
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async () => {
|
||||
const sizes = await
|
||||
getConnection().query(`
|
||||
SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size"
|
||||
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
||||
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
|
||||
AND C.relkind <> 'i'
|
||||
AND nspname !~ '^pg_toast';`)
|
||||
.then(recs => {
|
||||
const res = {} as Record<string, { count: number; size: number; }>;
|
||||
for (const rec of recs) {
|
||||
res[rec.table] = {
|
||||
count: parseInt(rec.count, 10),
|
||||
size: parseInt(rec.size, 10),
|
||||
};
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
return sizes;
|
||||
});
|
|
@ -8,6 +8,7 @@ import * as bcrypt from 'bcryptjs';
|
|||
import { Users, UserProfiles } from '../../../../models';
|
||||
import { ensure } from '../../../../prelude/ensure';
|
||||
import { sendEmail } from '../../../../services/send-email';
|
||||
import { ApiError } from '../../error';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
@ -27,6 +28,14 @@ export const meta = {
|
|||
email: {
|
||||
validator: $.optional.nullable.str
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
incorrectPassword: {
|
||||
message: 'Incorrect password.',
|
||||
code: 'INCORRECT_PASSWORD',
|
||||
id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -37,7 +46,7 @@ export default define(meta, async (ps, user) => {
|
|||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
||||
await UserProfiles.update({ userId: user.id }, {
|
||||
|
|
|
@ -6,6 +6,7 @@ import { fetchMeta } from '../../../misc/fetch-meta';
|
|||
import * as pkg from '../../../../package.json';
|
||||
import { Emojis } from '../../../models';
|
||||
import { types, bool } from '../../../misc/schema';
|
||||
import { getConnection } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
|
@ -114,6 +115,7 @@ export default define(meta, async (ps, me) => {
|
|||
machine: os.hostname(),
|
||||
os: os.platform(),
|
||||
node: process.version,
|
||||
psql: await getConnection().query('SHOW server_version').then(x => x[0].server_version),
|
||||
|
||||
cpu: {
|
||||
model: os.cpus()[0].model,
|
||||
|
|
|
@ -163,6 +163,19 @@ export default abstract class Chart<T extends Record<string, any>> {
|
|||
},
|
||||
...Chart.convertSchemaToFlatColumnDefinitions(schema)
|
||||
},
|
||||
indices: [{
|
||||
columns: ['date']
|
||||
}, {
|
||||
columns: ['span']
|
||||
}, {
|
||||
columns: ['group']
|
||||
}, {
|
||||
columns: ['span', 'date']
|
||||
}, {
|
||||
columns: ['date', 'group']
|
||||
}, {
|
||||
columns: ['span', 'date', 'group']
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
|
|
34
test/mfm.ts
34
test/mfm.ts
|
@ -804,6 +804,14 @@ describe('MFM', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('ignore trailing periods', () => {
|
||||
const tokens = parse('https://example.com...');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('url', { url: 'https://example.com' }),
|
||||
text('...')
|
||||
]);
|
||||
});
|
||||
|
||||
it('with comma', () => {
|
||||
const tokens = parse('https://example.com/foo?bar=a,b');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
|
@ -1116,6 +1124,14 @@ describe('MFM', () => {
|
|||
], {}),
|
||||
]);
|
||||
});
|
||||
|
||||
// https://misskey.io/notes/7u1kv5dmia
|
||||
it('ignore internal tilde', () => {
|
||||
const tokens = parse('~~~~~');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('~~~~~')
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('italic', () => {
|
||||
|
@ -1173,6 +1189,24 @@ describe('MFM', () => {
|
|||
text('foo_bar_baz'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('require spaces', () => {
|
||||
const tokens = parse('4日目_L38b a_b');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('4日目_L38b a_b'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('newline sandwich', () => {
|
||||
const tokens = parse('foo\n_bar_\nbaz');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo\n'),
|
||||
tree('italic', [
|
||||
text('bar')
|
||||
], {}),
|
||||
text('\nbaz'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue