1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-07-15 14:04:45 +02:00

Compare commits

..

25 Commits
6.3.2 ... 6.4.1

Author SHA1 Message Date
syuilo
b7adbbb9bf 6.4.1 2018-08-18 06:01:55 +09:00
syuilo
31aaf559ac Fix bug 2018-08-18 05:46:57 +09:00
syuilo
160185b108 Fix bug 2018-08-18 05:43:09 +09:00
syuilo
1d6b2bd969 6.4.0 2018-08-18 05:21:45 +09:00
syuilo
5df90fdc4a l10n 2018-08-18 05:15:39 +09:00
syuilo
607a5326d9 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-18 05:05:48 +09:00
syuilo
2cbd35acc4 🎨 2018-08-18 05:05:39 +09:00
Aya Morisawa
9a0224ee21 🎨 2018-08-18 05:00:54 +09:00
syuilo
2beedc5978 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-18 04:52:15 +09:00
syuilo
6d53e64798 Improve control panel 2018-08-18 04:52:06 +09:00
syuilo
3478cdea08 Merge pull request #2304 from acid-chicken/acid-chicken-patch-4
Update README.md
2018-08-18 04:39:36 +09:00
Acid Chicken (硫酸鶏)
b360f2ed45 Update README.md 2018-08-18 04:34:44 +09:00
syuilo
7b0c4c29b4 Merge pull request #2303 from syuilo/update-readme
Update README.md
2018-08-18 04:29:29 +09:00
Aya Morisawa
03f5d7575c Update README.md 2018-08-18 04:27:58 +09:00
syuilo
e922d8904c Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-18 04:22:00 +09:00
syuilo
45b55a8c98 Update rate limit setting 2018-08-18 04:21:49 +09:00
syuilo
8973b76bda Merge pull request #2302 from syuilo/kao-to-faces
Fix #2301
2018-08-18 04:20:57 +09:00
Aya Morisawa
41cf856e26 Fix #2301 2018-08-18 04:13:25 +09:00
syuilo
b7aeb10304 Merge pull request #2300 from syuilo/delete-autogen
Delete .autogen
2018-08-18 04:05:47 +09:00
Aya Morisawa
b8d9c1aa45 Delete .autogen 2018-08-18 04:05:00 +09:00
Aya Morisawa
d7dbe503b6 🎨 2018-08-18 03:59:56 +09:00
syuilo
c625dd074a Merge pull request #2299 from syuilo/new-kao
Add new kao
2018-08-18 03:56:30 +09:00
Aya Morisawa
f8a977f1c0 Add new kao 2018-08-18 03:55:45 +09:00
syuilo
c35e760fd3 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-18 03:52:46 +09:00
syuilo
bc34ac82cf Show some charts in control panel 2018-08-18 03:52:24 +09:00
22 changed files with 653 additions and 242 deletions

View File

@@ -1,69 +0,0 @@
<img src="https://github.com/syuilo/misskey/blob/b3f42e62af698a67c2250533c437569559f1fdf9/src/himasaku/resources/himasaku.png?raw=true" align="right" width="320px"/>
[![Misskey](/assets/title.png)](https://misskey.xyz/)
================================================================
[![][travis-badge]][travis-link]
[![][dependencies-badge]][dependencies-link]
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Greenkeeper badge](https://badges.greenkeeper.io/syuilo/misskey.svg)](https://greenkeeper.io/)
[Misskey](https://misskey.xyz) is an **advanced**, **redefined**, and ultimately **sophisticated** microblogging platform.
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
![](https://ja.mstdn.wiki/images/e/ed/Deck.jpg)
:sparkles: Features
----------------------------------------------------------------
* Rich text contents
* Reactions
* User lists
* Customizable column view (called MisskeyDeck)
* and widgets!
* Private messages
* ActivityPub support
and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).
:package: Create your instance
----------------------------------------------------------------
If you want to run your own instance of Misskey,
please see [Setup and installation guide](./docs/setup.en.md).
:wrench: Contribute
----------------------------------------------------------------
**[PR](https://github.com/syuilo/misskey/pulls)s welcome!**
If you want to...
* i18n ... please see [Translation guide](./docs/translate.en.md).
* l10n ... please visit https://crowdin.com/project/misskey
:heart: Backers & Sponsors
----------------------------------------------------------------
<!-- __PATREON__ -->
:four_leaf_clover: Copyright
----------------------------------------------------------------
> Copyright (c) 2014-2018 syuilo
Misskey is an open-source software licensed under the [GNU AGPLv3](LICENSE).
[![][agpl-3.0-badge]][AGPL-3.0]
[agpl-3.0]: https://www.gnu.org/licenses/agpl-3.0.en.html
[agpl-3.0-badge]: https://img.shields.io/badge/license-AGPL--3.0-444444.svg?style=flat-square
[travis-link]: https://travis-ci.org/syuilo/misskey
[travis-badge]: http://img.shields.io/travis/syuilo/misskey/master.svg?style=flat-square
[dependencies-link]: https://david-dm.org/syuilo/misskey
[dependencies-badge]: https://img.shields.io/david/syuilo/misskey.svg?style=flat-square
[backer-url]: #backers
[backer-badge]: https://opencollective.com/misskey/backers/badge.svg
[backers-image]: https://opencollective.com/misskey/backers.svg
[sponsor-url]: #sponsors
[sponsor-badge]: https://opencollective.com/misskey/sponsors/badge.svg
[sponsors-image]: https://opencollective.com/misskey/sponsors.svg
[support-url]: https://opencollective.com/misskey#support
[syuilo-link]: https://syuilo.com
[syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70

View File

@@ -7,9 +7,12 @@
[![][dependencies-badge]][dependencies-link]
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Greenkeeper badge](https://badges.greenkeeper.io/syuilo/misskey.svg)](https://greenkeeper.io/)
**Microblogging. Redefined.**
Sophisticated microblogging platform, evolving forever.
[Misskey](https://misskey.xyz) is an **advanced**, **redefined**, and ultimately **sophisticated** microblogging platform.
[Misskey](https://misskey.xyz) is a decentralized microblogging platform born on Earth.
Since it exists within the Fediverse (a universe where various social media platforms are organized),
it is mutually linked with other social media platforms.
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
@@ -27,7 +30,7 @@
and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).
:package: Create your instance
:package: Create your own instance
----------------------------------------------------------------
If you want to run your own instance of Misskey,
please see [Setup and installation guide](./docs/setup.en.md).
@@ -42,6 +45,7 @@ If you want to...
:heart: Backers & Sponsors
----------------------------------------------------------------
<!-- PATREON_START -->
<table>
<tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"></td>
@@ -70,6 +74,7 @@ If you want to...
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
</tr>
</table>
<!-- PATREON_END -->
:four_leaf_clover: Copyright
----------------------------------------------------------------

View File

@@ -931,6 +931,16 @@ desktop/views/pages/admin/admin.verify-user.vue:
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"

View File

@@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "6.3.2",
"clientVersion": "1.0.8498",
"version": "6.4.1",
"clientVersion": "1.0.8520",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,

View File

@@ -0,0 +1,10 @@
const faces = [
'(=^・・^=)',
'v(\'ω\')v',
'🐡( \'-\' 🐡 )フグパンチ!!!!',
'🖕(´・_・`)🖕',
'(。><。)',
'(Δ・x・Δ)'
];
export default () => faces[Math.floor(Math.random() * faces.length)];

View File

@@ -1,9 +0,0 @@
const kaos = [
'(=^・・^=)',
'v(\'ω\')v',
'🐡( \'-\' 🐡 )フグパンチ!!!!',
'🖕(´・_・`)🖕',
'(。><。)'
];
export default () => kaos[Math.floor(Math.random() * kaos.length)];

View File

@@ -58,7 +58,7 @@
import Vue from 'vue';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
import getKao from '../../../common/scripts/get-kao';
import getFace from '../../../common/scripts/get-face';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import parse from '../../../../../mfm/parse';
import { host } from '../../../config';
@@ -421,7 +421,7 @@ export default Vue.extend({
},
kao() {
this.text += getKao();
this.text += getFace();
}
}
});

View File

@@ -0,0 +1,145 @@
<template>
<div class="zyknedwtlthezamcjlolyusmipqmjgxz">
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<defs>
<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
</linearGradient>
<mask :id="cpuMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
<polygon
:points="cpuPolygonPoints"
fill="#fff"
fill-opacity="0.5"/>
<polyline
:points="cpuPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="0.3"/>
</mask>
</defs>
<rect
x="0" y="0"
:width="viewBoxX" :height="viewBoxY"
:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/>
<text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text>
</svg>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<defs>
<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
</linearGradient>
<mask :id="memMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
<polygon
:points="memPolygonPoints"
fill="#fff"
fill-opacity="0.5"/>
<polyline
:points="memPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="0.3"/>
</mask>
</defs>
<rect
x="0" y="0"
:width="viewBoxX" :height="viewBoxY"
:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/>
<text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text>
</svg>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import * as uuid from 'uuid';
export default Vue.extend({
props: ['connection'],
data() {
return {
viewBoxX: 50,
viewBoxY: 20,
stats: [],
cpuGradientId: uuid(),
cpuMaskId: uuid(),
memGradientId: uuid(),
memMaskId: uuid(),
cpuPolylinePoints: '',
memPolylinePoints: '',
cpuPolygonPoints: '',
memPolygonPoints: '',
cpuP: '',
memP: ''
};
},
mounted() {
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send({
type: 'requestLog',
id: Math.random().toString()
});
},
beforeDestroy() {
this.connection.off('stats', this.onStats);
this.connection.off('statsLog', this.onStatsLog);
},
methods: {
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > 50) this.stats.shift();
const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu_usage) * this.viewBoxY]);
const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.used / s.mem.total)) * this.viewBoxY]);
this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
this.cpuP = (stats.cpu_usage * 100).toFixed(0);
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
},
onStatsLog(statsLog) {
statsLog.forEach(stats => this.onStats(stats));
}
}
});
</script>
<style lang="stylus" scoped>
root(isDark)
margin-bottom 16px
> svg
display block
width 50%
float left
&:first-child
padding-right 5px
&:last-child
padding-left 5px
> text
font-size 2px
fill isDark ? rgba(#fff, 0.55) : rgba(#000, 0.55)
> tspan
opacity 0.5
&:after
content ""
display block
clear both
.zyknedwtlthezamcjlolyusmipqmjgxz[data-darkmode]
root(true)
.zyknedwtlthezamcjlolyusmipqmjgxz:not([data-darkmode])
root(false)
</style>

View File

@@ -1,11 +1,14 @@
<template>
<div>
<h1>%i18n:@dashboard%</h1>
<div v-if="stats">
<p><b>%i18n:@all-users%</b>: <span>{{ stats.usersCount | number }}</span></p>
<p><b>%i18n:@original-users%</b>: <span>{{ stats.originalUsersCount | number }}</span></p>
<p><b>%i18n:@all-notes%</b>: <span>{{ stats.notesCount | number }}</span></p>
<p><b>%i18n:@original-notes%</b>: <span>{{ stats.originalNotesCount | number }}</span></p>
<div class="obdskegsannmntldydackcpzezagxqfy card">
<header>%i18n:@dashboard%</header>
<div v-if="stats" class="stats">
<div><b>%fa:user% {{ stats.originalUsersCount | number }}</b><span>%i18n:@original-users%</span></div>
<div><span>%fa:user% {{ stats.usersCount | number }}</span><span>%i18n:@all-users%</span></div>
<div><b>%fa:pen% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div>
<div><span>%fa:pen% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div>
</div>
<div class="cpu-memory">
<x-cpu-memory :connection="connection"/>
</div>
<div>
<button class="ui" @click="invite">%i18n:@invite%</button>
@@ -16,19 +19,31 @@
<script lang="ts">
import Vue from "vue";
import XCpuMemory from "./admin.cpu-memory.vue";
export default Vue.extend({
components: {
XCpuMemory
},
data() {
return {
stats: null,
inviteCode: null
inviteCode: null,
connection: null,
connectionId: null
};
},
created() {
this.connection = (this as any).os.streams.serverStatsStream.getConnection();
this.connectionId = (this as any).os.streams.serverStatsStream.use();
(this as any).api('stats').then(stats => {
this.stats = stats;
});
},
beforeDestroy() {
(this as any).os.streams.serverStatsStream.dispose(this.connectionId);
},
methods: {
invite() {
(this as any).api('admin/invite').then(x => {
@@ -40,10 +55,26 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
h1
margin 0 0 1em 0
padding 0 0 8px 0
font-size 1em
color #555
border-bottom solid 1px #eee
@import '~const.styl'
.obdskegsannmntldydackcpzezagxqfy
> .stats
display flex
justify-content center
margin-bottom 16px
padding 16px
border solid 1px #eee
border-radius 8px
> div
flex 1
text-align center
> *:first-child
display block
color $theme-color
> *:last-child
font-size 70%
</style>

View File

@@ -0,0 +1,81 @@
<template>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<polyline
:points="pointsNote"
fill="none"
stroke-width="1"
stroke="#41ddde"/>
<polyline
:points="pointsReply"
fill="none"
stroke-width="1"
stroke="#f7796c"/>
<polyline
:points="pointsRenote"
fill="none"
stroke-width="1"
stroke="#a1de41"/>
<polyline
:points="pointsTotal"
fill="none"
stroke-width="1"
stroke="#555"
stroke-dasharray="2 2"/>
</svg>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
data: {
required: true
},
type: {
type: String,
required: true
}
},
data() {
return {
chart: this.data,
viewBoxX: 365,
viewBoxY: 70,
pointsNote: null,
pointsReply: null,
pointsRenote: null,
pointsTotal: null
};
},
created() {
this.chart.forEach(d => {
d.notes = this.type == 'local' ? d.localNotes : d.remoteNotes;
d.replies = this.type == 'local' ? d.localReplies : d.remoteReplies;
d.renotes = this.type == 'local' ? d.localRenotes : d.remoteRenotes;
});
this.chart.forEach(d => {
d.total = d.notes + d.replies + d.renotes;
});
const peak = Math.max.apply(null, this.chart.map(d => d.total));
if (peak != 0) {
const data = this.chart.slice().reverse();
this.pointsNote = data.map((d, i) => `${i},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' ');
this.pointsReply = data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' ');
this.pointsRenote = data.map((d, i) => `${i},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' ');
this.pointsTotal = data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ');
}
}
});
</script>
<style lang="stylus" scoped>
svg
display block
padding 10px
width 100%
</style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="card">
<header>%i18n:@title%</header>
<div class="card">
<header>%i18n:@local%</header>
<x-chart v-if="data" :data="data" type="local"/>
</div>
<div class="card">
<header>%i18n:@remote%</header>
<x-chart v-if="data" :data="data" type="remote"/>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import XChart from "./admin.notes-chart.chart.vue";
export default Vue.extend({
components: {
XChart
},
data() {
return {
data: null
};
},
created() {
(this as any).api('aggregation/notes').then(res => {
this.data = res;
});
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div class="card">
<header>%i18n:@suspend-user%</header>
<input v-model="username" type="text" class="ui"/>
<button class="ui" @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button>

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div class="card">
<header>%i18n:@unsuspend-user%</header>
<input v-model="username" type="text" class="ui"/>
<button class="ui" @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</button>

View File

@@ -0,0 +1,53 @@
<template>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<polyline
:points="points"
fill="none"
stroke-width="1"
stroke="#555"/>
</svg>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
data: {
required: true
},
type: {
type: String,
required: true
}
},
data() {
return {
chart: this.data,
viewBoxX: 365,
viewBoxY: 70,
points: null
};
},
created() {
this.chart.forEach(d => {
d.count = this.type == 'local' ? d.local : d.remote;
});
const peak = Math.max.apply(null, this.chart.map(d => d.count));
if (peak != 0) {
const data = this.chart.slice().reverse();
this.points = data.map((d, i) => `${i},${(1 - (d.count / peak)) * this.viewBoxY}`).join(' ');
}
}
});
</script>
<style lang="stylus" scoped>
svg
display block
padding 10px
width 100%
</style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="card">
<header>%i18n:@title%</header>
<div class="card">
<header>%i18n:@local%</header>
<x-chart v-if="data" :data="data" type="local"/>
</div>
<div class="card">
<header>%i18n:@remote%</header>
<x-chart v-if="data" :data="data" type="remote"/>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import XChart from "./admin.users-chart.chart.vue";
export default Vue.extend({
components: {
XChart
},
data() {
return {
data: null
};
},
created() {
(this as any).api('aggregation/users').then(res => {
this.data = res;
});
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div class="card">
<header>%i18n:@verify-user%</header>
<input v-model="username" type="text" class="ui"/>
<button class="ui" @click="verifyUser" :disabled="verifying">%i18n:@verify%</button>

View File

@@ -9,8 +9,10 @@
</ul>
</nav>
<main>
<div v-if="page == 'dashboard'">
<div v-show="page == 'dashboard'">
<x-dashboard/>
<x-users-chart/>
<x-notes-chart/>
</div>
<div v-if="page == 'users'">
<x-suspend-user/>
@@ -29,13 +31,17 @@ import XDashboard from "./admin.dashboard.vue";
import XSuspendUser from "./admin.suspend-user.vue";
import XUnsuspendUser from "./admin.unsuspend-user.vue";
import XVerifyUser from "./admin.verify-user.vue";
import XUsersChart from "./admin.users-chart.vue";
import XNotesChart from "./admin.notes-chart.vue";
export default Vue.extend({
components: {
XDashboard,
XSuspendUser,
XUnsuspendUser,
XVerifyUser
XVerifyUser,
XUsersChart,
XNotesChart
},
data() {
return {
@@ -50,7 +56,7 @@ export default Vue.extend({
});
</script>
<style lang="stylus" scoped>
<style lang="stylus">
@import '~const.styl'
.mk-admin
@@ -95,19 +101,21 @@ export default Vue.extend({
> div
> div
margin-bottom 16px
padding 32px
max-width 800px
background #fff
box-shadow 0 2px 8px rgba(#000, 0.1)
header
margin 10px 0
.card
padding 32px
background #fff
box-shadow 0 2px 8px rgba(#000, 0.1)
&:not(:last-child)
margin-bottom 16px
button
margin 16px 0
position absolute
right 0
> header
margin 0 0 1em 0
padding 0 0 8px 0
font-size 1em
color #555
border-bottom solid 1px #eee
</style>

View File

@@ -56,7 +56,7 @@ import Vue from 'vue';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import getKao from '../../../common/scripts/get-kao';
import getFace from '../../../common/scripts/get-face';
import parse from '../../../../../mfm/parse';
import { host } from '../../../config';
@@ -313,7 +313,7 @@ export default Vue.extend({
},
kao() {
this.text += getKao();
this.text += getFace();
}
}
});

View File

@@ -0,0 +1,116 @@
import Note from '../../../../models/note';
export const meta = {
requireCredential: true,
requireAdmin: true
};
/**
* Aggregate notes
*/
export default (params: any) => new Promise(async (res, rej) => {
const query = [{
$match: {
createdAt: {
$gt: new Date(new Date().setFullYear(new Date().getFullYear() - 1))
}
}
}, {
$project: {
renoteId: '$renoteId',
replyId: '$replyId',
user: '$_user',
createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST
}
}, {
$project: {
date: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' }
},
type: {
$cond: {
if: { $ne: ['$renoteId', null] },
then: 'renote',
else: {
$cond: {
if: { $ne: ['$replyId', null] },
then: 'reply',
else: 'note'
}
}
}
},
origin: {
$cond: {
if: { $eq: ['$user.host', null] },
then: 'local',
else: 'remote'
}
}
}
}, {
$group: {
_id: {
date: '$date',
type: '$type',
origin: '$origin'
},
count: { $sum: 1 }
}
}, {
$group: {
_id: '$_id.date',
data: {
$addToSet: {
type: '$_id.type',
origin: '$_id.origin',
count: '$count'
}
}
}
}] as any;
const datas = await Note.aggregate(query);
datas.forEach((data: any) => {
data.date = data._id;
delete data._id;
data.localNotes = (data.data.filter((x: any) => x.type == 'note' && x.origin == 'local')[0] || { count: 0 }).count;
data.localRenotes = (data.data.filter((x: any) => x.type == 'renote' && x.origin == 'local')[0] || { count: 0 }).count;
data.localReplies = (data.data.filter((x: any) => x.type == 'reply' && x.origin == 'local')[0] || { count: 0 }).count;
data.remoteNotes = (data.data.filter((x: any) => x.type == 'note' && x.origin == 'remote')[0] || { count: 0 }).count;
data.remoteRenotes = (data.data.filter((x: any) => x.type == 'renote' && x.origin == 'remote')[0] || { count: 0 }).count;
data.remoteReplies = (data.data.filter((x: any) => x.type == 'reply' && x.origin == 'remote')[0] || { count: 0 }).count;
delete data.data;
});
const graph = [];
for (let i = 0; i < 365; i++) {
const day = new Date(new Date().setDate(new Date().getDate() - i));
const data = datas.filter((d: any) =>
d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate()
)[0];
if (data) {
graph.push(data);
} else {
graph.push({
date: { year: day.getFullYear(), month: day.getMonth() + 1, day: day.getDate() },
localNotes: 0,
localRenotes: 0,
localReplies: 0,
remoteNotes: 0,
remoteRenotes: 0,
remoteReplies: 0
});
}
}
res(graph);
});

View File

@@ -1,84 +0,0 @@
import $ from 'cafy';
import Note from '../../../../models/note';
/**
* Aggregate notes
*/
export default (params: any) => new Promise(async (res, rej) => {
// Get 'limit' parameter
const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit);
if (limitErr) return rej('invalid limit param');
const datas = await Note
.aggregate([
{ $project: {
renoteId: '$renoteId',
replyId: '$replyId',
createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST
}},
{ $project: {
date: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' }
},
type: {
$cond: {
if: { $ne: ['$renoteId', null] },
then: 'renote',
else: {
$cond: {
if: { $ne: ['$replyId', null] },
then: 'reply',
else: 'note'
}
}
}
}}
},
{ $group: { _id: {
date: '$date',
type: '$type'
}, count: { $sum: 1 } } },
{ $group: {
_id: '$_id.date',
data: { $addToSet: {
type: '$_id.type',
count: '$count'
}}
} }
]);
datas.forEach((data: any) => {
data.date = data._id;
delete data._id;
data.notes = (data.data.filter((x: any) => x.type == 'note')[0] || { count: 0 }).count;
data.renotes = (data.data.filter((x: any) => x.type == 'renote')[0] || { count: 0 }).count;
data.replies = (data.data.filter((x: any) => x.type == 'reply')[0] || { count: 0 }).count;
delete data.data;
});
const graph = [];
for (let i = 0; i < limit; i++) {
const day = new Date(new Date().setDate(new Date().getDate() - i));
const data = datas.filter((d: any) =>
d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate()
)[0];
if (data) {
graph.push(data);
} else {
graph.push({
notes: 0,
renotes: 0,
replies: 0
});
}
}
res(graph);
});

View File

@@ -1,54 +1,91 @@
import $ from 'cafy';
import User from '../../../../models/user';
export const meta = {
requireCredential: true,
requireAdmin: true
};
/**
* Aggregate users
*/
export default (params: any) => new Promise(async (res, rej) => {
// Get 'limit' parameter
const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit);
if (limitErr) return rej('invalid limit param');
const users = await User
.find({}, {
sort: {
_id: -1
},
fields: {
_id: false,
createdAt: true,
deletedAt: true
const query = [{
$match: {
createdAt: {
$gt: new Date(new Date().setFullYear(new Date().getFullYear() - 1))
}
});
}
}, {
$project: {
host: '$host',
createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST
}
}, {
$project: {
date: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' }
},
origin: {
$cond: {
if: { $eq: ['$host', null] },
then: 'local',
else: 'remote'
}
}
}
}, {
$group: {
_id: {
date: '$date',
origin: '$origin'
},
count: { $sum: 1 }
}
}, {
$group: {
_id: '$_id.date',
data: {
$addToSet: {
type: '$_id.type',
origin: '$_id.origin',
count: '$count'
}
}
}
}] as any;
const datas = await User.aggregate(query);
datas.forEach((data: any) => {
data.date = data._id;
delete data._id;
data.local = (data.data.filter((x: any) => x.origin == 'local')[0] || { count: 0 }).count;
data.remote = (data.data.filter((x: any) => x.origin == 'remote')[0] || { count: 0 }).count;
delete data.data;
});
const graph = [];
for (let i = 0; i < limit; i++) {
let dayStart = new Date(new Date().setDate(new Date().getDate() - i));
dayStart = new Date(dayStart.setMilliseconds(0));
dayStart = new Date(dayStart.setSeconds(0));
dayStart = new Date(dayStart.setMinutes(0));
dayStart = new Date(dayStart.setHours(0));
for (let i = 0; i < 365; i++) {
const day = new Date(new Date().setDate(new Date().getDate() - i));
let dayEnd = new Date(new Date().setDate(new Date().getDate() - i));
dayEnd = new Date(dayEnd.setMilliseconds(999));
dayEnd = new Date(dayEnd.setSeconds(59));
dayEnd = new Date(dayEnd.setMinutes(59));
dayEnd = new Date(dayEnd.setHours(23));
// day = day.getTime();
const data = datas.filter((d: any) =>
d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate()
)[0];
const total = users.filter(u =>
u.createdAt < dayEnd && (u.deletedAt == null || u.deletedAt > dayEnd)
).length;
const created = users.filter(u =>
u.createdAt < dayEnd && u.createdAt > dayStart
).length;
graph.push({
total: total,
created: created
});
if (data) {
graph.push(data);
} else {
graph.push({
date: { year: day.getFullYear(), month: day.getMonth() + 1, day: day.getDate() },
local: 0,
remote: 0
});
}
}
res(graph);

View File

@@ -16,8 +16,7 @@ export const meta = {
limit: {
duration: ms('1hour'),
max: 300,
minInterval: ms('1second')
max: 300
},
kind: 'note-write',