mirror of
https://github.com/mastodon/mastodon.git
synced 2025-07-12 15:33:14 +00:00
Compare commits
9 Commits
d747651b99
...
56f8b67c19
Author | SHA1 | Date | |
---|---|---|---|
![]() |
56f8b67c19 | ||
![]() |
3b52dca405 | ||
![]() |
853a0c466e | ||
![]() |
5910d5096d | ||
![]() |
010688a67a | ||
![]() |
c98dfb827b | ||
![]() |
7535ad01d6 | ||
![]() |
53a4a3c8cf | ||
![]() |
39d2f132e8 |
|
@ -21,6 +21,15 @@ module Admin
|
||||||
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
|
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def availability
|
||||||
|
authorize :instance, :show?
|
||||||
|
end
|
||||||
|
|
||||||
|
def statistics
|
||||||
|
authorize :instance, :show?
|
||||||
|
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
authorize :instance, :destroy?
|
authorize :instance, :destroy?
|
||||||
Admin::DomainPurgeWorker.perform_async(@instance.domain)
|
Admin::DomainPurgeWorker.perform_async(@instance.domain)
|
||||||
|
@ -31,7 +40,7 @@ module Admin
|
||||||
def clear_delivery_errors
|
def clear_delivery_errors
|
||||||
authorize :delivery, :clear_delivery_errors?
|
authorize :delivery, :clear_delivery_errors?
|
||||||
@instance.delivery_failure_tracker.clear_failures!
|
@instance.delivery_failure_tracker.clear_failures!
|
||||||
redirect_to admin_instance_path(@instance.domain)
|
redirect_to availability_admin_instance_path(@instance.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def restart_delivery
|
def restart_delivery
|
||||||
|
@ -42,14 +51,14 @@ module Admin
|
||||||
log_action :destroy, @instance.unavailable_domain
|
log_action :destroy, @instance.unavailable_domain
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_instance_path(@instance.domain)
|
redirect_to availability_admin_instance_path(@instance.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def stop_delivery
|
def stop_delivery
|
||||||
authorize :delivery, :stop_delivery?
|
authorize :delivery, :stop_delivery?
|
||||||
unavailable_domain = UnavailableDomain.create!(domain: @instance.domain)
|
unavailable_domain = UnavailableDomain.create!(domain: @instance.domain)
|
||||||
log_action :create, unavailable_domain
|
log_action :create, unavailable_domain
|
||||||
redirect_to admin_instance_path(@instance.domain)
|
redirect_to availability_admin_instance_path(@instance.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -26,6 +26,12 @@ module ContextHelper
|
||||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||||
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
|
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
|
||||||
quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' },
|
quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' },
|
||||||
|
quotes: {
|
||||||
|
'quote' => 'https://w3id.org/fep/044f#quote',
|
||||||
|
'quoteUri' => 'http://fedibird.com/ns#quoteUri',
|
||||||
|
'_misskey_quote' => 'https://misskey-hub.net/ns#_misskey_quote',
|
||||||
|
'quoteAuthorization' => 'https://w3id.org/fep/044f#quoteAuthorization',
|
||||||
|
},
|
||||||
interaction_policies: {
|
interaction_policies: {
|
||||||
'gts' => 'https://gotosocial.org/ns#',
|
'gts' => 'https://gotosocial.org/ns#',
|
||||||
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },
|
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },
|
||||||
|
|
|
@ -1,12 +1,30 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useLinks } from 'mastodon/hooks/useLinks';
|
import { useLinks } from 'mastodon/hooks/useLinks';
|
||||||
|
|
||||||
export const AccountBio: React.FC<{
|
interface AccountBioProps {
|
||||||
note: string;
|
note: string;
|
||||||
className: string;
|
className: string;
|
||||||
}> = ({ note, className }) => {
|
dropdownAccountId?: string;
|
||||||
const handleClick = useLinks();
|
}
|
||||||
|
|
||||||
if (note.length === 0 || note === '<p></p>') {
|
export const AccountBio: React.FC<AccountBioProps> = ({
|
||||||
|
note,
|
||||||
|
className,
|
||||||
|
dropdownAccountId,
|
||||||
|
}) => {
|
||||||
|
const handleClick = useLinks(!!dropdownAccountId);
|
||||||
|
const handleNodeChange = useCallback(
|
||||||
|
(node: HTMLDivElement | null) => {
|
||||||
|
if (!dropdownAccountId || !node || node.childNodes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDropdownToHashtags(node, dropdownAccountId);
|
||||||
|
},
|
||||||
|
[dropdownAccountId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (note.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +33,28 @@ export const AccountBio: React.FC<{
|
||||||
className={`${className} translate`}
|
className={`${className} translate`}
|
||||||
dangerouslySetInnerHTML={{ __html: note }}
|
dangerouslySetInnerHTML={{ __html: note }}
|
||||||
onClickCapture={handleClick}
|
onClickCapture={handleClick}
|
||||||
|
ref={handleNodeChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function addDropdownToHashtags(node: HTMLElement | null, accountId: string) {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const childNode of node.childNodes) {
|
||||||
|
if (!(childNode instanceof HTMLElement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
childNode instanceof HTMLAnchorElement &&
|
||||||
|
(childNode.classList.contains('hashtag') ||
|
||||||
|
childNode.innerText.startsWith('#')) &&
|
||||||
|
!childNode.dataset.menuHashtag
|
||||||
|
) {
|
||||||
|
childNode.dataset.menuHashtag = accountId;
|
||||||
|
} else if (childNode.childNodes.length > 0) {
|
||||||
|
addDropdownToHashtags(childNode, accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default class Dimension extends PureComponent {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
} else {
|
} else if (data[0].data.length > 0) {
|
||||||
const sum = data[0].data.reduce((sum, cur) => sum + (cur.value * 1), 0);
|
const sum = data[0].data.reduce((sum, cur) => sum + (cur.value * 1), 0);
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
|
@ -81,6 +81,16 @@ export default class Dimension extends PureComponent {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr className='dimension__item'>
|
||||||
|
<td>No data available</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,6 +6,7 @@ import classNames from 'classnames';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AccountBio } from '@/mastodon/components/account_bio';
|
||||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||||
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
||||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
|
@ -773,7 +774,6 @@ export const AccountHeader: React.FC<{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = { __html: account.note_emojified };
|
|
||||||
const displayNameHtml = { __html: account.display_name_html };
|
const displayNameHtml = { __html: account.display_name_html };
|
||||||
const fields = account.fields;
|
const fields = account.fields;
|
||||||
const isLocal = !account.acct.includes('@');
|
const isLocal = !account.acct.includes('@');
|
||||||
|
@ -897,12 +897,11 @@ export const AccountHeader: React.FC<{
|
||||||
<AccountNote accountId={accountId} />
|
<AccountNote accountId={accountId} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{account.note.length > 0 && account.note !== '<p></p>' && (
|
<AccountBio
|
||||||
<div
|
note={account.note_emojified}
|
||||||
className='account__header__content translate'
|
dropdownAccountId={accountId}
|
||||||
dangerouslySetInnerHTML={content}
|
className='account__header__content'
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='account__header__fields'>
|
<div className='account__header__fields'>
|
||||||
<dl>
|
<dl>
|
||||||
|
|
|
@ -8,13 +8,14 @@ import { openURL } from 'mastodon/actions/search';
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
import { useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
const isMentionClick = (element: HTMLAnchorElement) =>
|
const isMentionClick = (element: HTMLAnchorElement) =>
|
||||||
element.classList.contains('mention');
|
element.classList.contains('mention') &&
|
||||||
|
!element.classList.contains('hashtag');
|
||||||
|
|
||||||
const isHashtagClick = (element: HTMLAnchorElement) =>
|
const isHashtagClick = (element: HTMLAnchorElement) =>
|
||||||
element.textContent?.[0] === '#' ||
|
element.textContent?.[0] === '#' ||
|
||||||
element.previousSibling?.textContent?.endsWith('#');
|
element.previousSibling?.textContent?.endsWith('#');
|
||||||
|
|
||||||
export const useLinks = () => {
|
export const useLinks = (skipHashtags?: boolean) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
@ -61,12 +62,12 @@ export const useLinks = () => {
|
||||||
if (isMentionClick(target)) {
|
if (isMentionClick(target)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void handleMentionClick(target);
|
void handleMentionClick(target);
|
||||||
} else if (isHashtagClick(target)) {
|
} else if (isHashtagClick(target) && !skipHashtags) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleHashtagClick(target);
|
handleHashtagClick(target);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleMentionClick, handleHashtagClick],
|
[skipHashtags, handleMentionClick, handleHashtagClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
return handleClick;
|
return handleClick;
|
||||||
|
|
|
@ -126,6 +126,9 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
||||||
? accountJSON.username
|
? accountJSON.username
|
||||||
: accountJSON.display_name;
|
: accountJSON.display_name;
|
||||||
|
|
||||||
|
const accountNote =
|
||||||
|
accountJSON.note && accountJSON.note !== '<p></p>' ? accountJSON.note : '';
|
||||||
|
|
||||||
return AccountFactory({
|
return AccountFactory({
|
||||||
...accountJSON,
|
...accountJSON,
|
||||||
moved: moved?.id,
|
moved: moved?.id,
|
||||||
|
@ -142,8 +145,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
||||||
escapeTextContentForBrowser(displayName),
|
escapeTextContentForBrowser(displayName),
|
||||||
emojiMap,
|
emojiMap,
|
||||||
),
|
),
|
||||||
note_emojified: emojify(accountJSON.note, emojiMap),
|
note_emojified: emojify(accountNote, emojiMap),
|
||||||
note_plain: unescapeHTML(accountJSON.note),
|
note_plain: unescapeHTML(accountNote),
|
||||||
url:
|
url:
|
||||||
accountJSON.url.startsWith('http://') ||
|
accountJSON.url.startsWith('http://') ||
|
||||||
accountJSON.url.startsWith('https://')
|
accountJSON.url.startsWith('https://')
|
||||||
|
|
|
@ -309,6 +309,27 @@ $content-width: 840px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lead {
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: $secondary-text-color;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $highlight-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.with-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.fields-group h6 {
|
.fields-group h6 {
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -609,6 +630,25 @@ body,
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.simple_form {
|
||||||
|
.actions {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
&__button--end {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.batch-form-box {
|
.batch-form-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -1835,7 +1875,7 @@ a.sparkline {
|
||||||
.availability-indicator {
|
.availability-indicator {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
|
|
||||||
|
|
|
@ -62,11 +62,20 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
||||||
grid-gap: 10px;
|
grid-gap: 10px;
|
||||||
|
align-content: start;
|
||||||
|
|
||||||
@media screen and (width <= 1350px) {
|
@media screen and (width <= 1350px) {
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__hint {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
grid-column: span 3;
|
||||||
|
align-items: center;
|
||||||
|
color: $darker-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
&--span-double-column {
|
&--span-double-column {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
|
@ -118,4 +127,8 @@
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--instance-overview {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ class DeliveryFailureTracker
|
||||||
include Redisable
|
include Redisable
|
||||||
|
|
||||||
FAILURE_DAYS_THRESHOLD = 7
|
FAILURE_DAYS_THRESHOLD = 7
|
||||||
|
MEASURED_DAYS = 14
|
||||||
|
|
||||||
def initialize(url_or_host)
|
def initialize(url_or_host)
|
||||||
@host = url_or_host.start_with?('https://', 'http://') ? Addressable::URI.parse(url_or_host).normalized_host : url_or_host
|
@host = url_or_host.start_with?('https://', 'http://') ? Addressable::URI.parse(url_or_host).normalized_host : url_or_host
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
%br/
|
%br/
|
||||||
|
|
||||||
= f.object.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
|
= f.object.policies.map { |policy| t(policy, scope: 'admin.instances.federation_policies.policies') }.join(' · ')
|
||||||
- if f.object.public_comment.present?
|
- if f.object.public_comment.present?
|
||||||
·
|
·
|
||||||
= f.object.public_comment
|
= f.object.public_comment
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
-# locals: (instance_domain:, period_end_at:, period_start_at:)
|
-# locals: (instance_domain:, period_end_at:, period_start_at:)
|
||||||
%p
|
|
||||||
= material_symbol 'info'
|
|
||||||
= t('admin.instances.totals_time_period_hint_html')
|
|
||||||
|
|
||||||
.dashboard
|
.dashboard.dashboard--instance-overview
|
||||||
.dashboard__item
|
.dashboard__item
|
||||||
= react_admin_component :counter,
|
= react_admin_component :counter,
|
||||||
end_at: period_end_at,
|
end_at: period_end_at,
|
||||||
|
@ -48,19 +45,6 @@
|
||||||
measure: 'instance_reports',
|
measure: 'instance_reports',
|
||||||
params: { domain: instance_domain },
|
params: { domain: instance_domain },
|
||||||
start_at: period_start_at
|
start_at: period_start_at
|
||||||
.dashboard__item
|
%p.dashboard__hint
|
||||||
= react_admin_component :dimension,
|
= material_symbol 'info'
|
||||||
dimension: 'instance_accounts',
|
= t('admin.instances.totals_time_period_hint_html')
|
||||||
end_at: period_end_at,
|
|
||||||
label: t('admin.instances.dashboard.instance_accounts_dimension'),
|
|
||||||
limit: 8,
|
|
||||||
params: { domain: instance_domain },
|
|
||||||
start_at: period_start_at
|
|
||||||
.dashboard__item
|
|
||||||
= react_admin_component :dimension,
|
|
||||||
dimension: 'instance_languages',
|
|
||||||
end_at: period_end_at,
|
|
||||||
label: t('admin.instances.dashboard.instance_languages_dimension'),
|
|
||||||
limit: 8,
|
|
||||||
params: { domain: instance_domain },
|
|
||||||
start_at: period_start_at
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
%small
|
%small
|
||||||
- if instance.domain_block
|
- if instance.domain_block
|
||||||
= instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
|
= instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.federation_policies.policies') }.join(' · ')
|
||||||
- if instance.domain_block.public_comment.present?
|
- if instance.domain_block.public_comment.present?
|
||||||
%span.comment.public-comment #{t('admin.domain_blocks.public_comment')}: #{instance.domain_block.public_comment}
|
%span.comment.public-comment #{t('admin.domain_blocks.public_comment')}: #{instance.domain_block.public_comment}
|
||||||
- if instance.domain_block.private_comment.present?
|
- if instance.domain_block.private_comment.present?
|
||||||
|
|
47
app/views/admin/instances/availability.html.haml
Normal file
47
app/views/admin/instances/availability.html.haml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= @instance.domain
|
||||||
|
|
||||||
|
- content_for :heading do
|
||||||
|
%h2= t('admin.instances.instance.title', domain: @instance.domain)
|
||||||
|
= render partial: 'admin/instances/shared/links', locals: { instance: @instance }
|
||||||
|
|
||||||
|
%p.lead= t('admin.instances.availability.subtitle')
|
||||||
|
|
||||||
|
- if @instance.persisted?
|
||||||
|
%p
|
||||||
|
= t('admin.instances.availability.description_html', count: DeliveryFailureTracker::FAILURE_DAYS_THRESHOLD)
|
||||||
|
= t('admin.instances.availability.period_description', count: DeliveryFailureTracker::MEASURED_DAYS)
|
||||||
|
|
||||||
|
.availability-indicator
|
||||||
|
%ul.availability-indicator__graphic
|
||||||
|
- @instance.availability_over_days(DeliveryFailureTracker::MEASURED_DAYS).each do |(date, failing)|
|
||||||
|
%li.availability-indicator__graphic__item{ class: failing ? 'negative' : 'neutral', title: l(date) }
|
||||||
|
.availability-indicator__hint
|
||||||
|
- if @instance.unavailable?
|
||||||
|
%span.negative-hint
|
||||||
|
= t('admin.instances.availability.failure_threshold_reached', date: l(@instance.unavailable_domain.created_at.to_date))
|
||||||
|
- elsif @instance.exhausted_deliveries_days.empty?
|
||||||
|
%span.positive-hint
|
||||||
|
= t('admin.instances.availability.no_failures_recorded')
|
||||||
|
- else
|
||||||
|
%span.negative-hint
|
||||||
|
= t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
|
||||||
|
|
||||||
|
.button-group
|
||||||
|
- if @instance.unavailable?
|
||||||
|
= link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button'
|
||||||
|
- elsif @instance.exhausted_deliveries_days.empty?
|
||||||
|
= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button button--destructive'
|
||||||
|
- else
|
||||||
|
= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button button-secondary' unless @instance.exhausted_deliveries_days.empty?
|
||||||
|
= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button button--destructive'
|
||||||
|
|
||||||
|
%hr.spacer
|
||||||
|
|
||||||
|
- if @instance.purgeable?
|
||||||
|
%h3= t('admin.instances.purge_title')
|
||||||
|
%p= t('admin.instances.purge_description_html')
|
||||||
|
|
||||||
|
= link_to t('admin.instances.purge'), admin_instance_path(@instance), data: { confirm: t('admin.instances.confirm_purge'), method: :delete }, class: 'button button--destructive'
|
||||||
|
- else
|
||||||
|
%p= t('admin.instances.availability.unknown_instance')
|
6
app/views/admin/instances/shared/_links.html.haml
Normal file
6
app/views/admin/instances/shared/_links.html.haml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.content__heading__tabs
|
||||||
|
= render_navigation renderer: :links do |primary|
|
||||||
|
:ruby
|
||||||
|
primary.item :overview, safe_join([material_symbol('cloud'), t('admin.instances.title')]), admin_instance_path(instance)
|
||||||
|
primary.item :statistics, safe_join([material_symbol('trending_up'), t('admin.instances.statistics.title')]), statistics_admin_instance_path(instance)
|
||||||
|
primary.item :availability, safe_join([material_symbol('warning'), t('admin.instances.availability.title')]), availability_admin_instance_path(instance)
|
|
@ -1,6 +1,10 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= @instance.domain
|
= @instance.domain
|
||||||
|
|
||||||
|
- content_for :heading do
|
||||||
|
%h2= t('admin.instances.instance.title', domain: @instance.domain)
|
||||||
|
= render partial: 'admin/instances/shared/links', locals: { instance: @instance }
|
||||||
|
|
||||||
- if current_user.can?(:view_dashboard)
|
- if current_user.can?(:view_dashboard)
|
||||||
- content_for :heading_actions do
|
- content_for :heading_actions do
|
||||||
= date_range(@time_period)
|
= date_range(@time_period)
|
||||||
|
@ -13,34 +17,49 @@
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
%h3= t('admin.instances.content_policies.title')
|
%h3= t('admin.instances.federation_policies.title')
|
||||||
|
|
||||||
- if limited_federation_mode?
|
- if limited_federation_mode?
|
||||||
%p= t('admin.instances.content_policies.limited_federation_mode_description_html')
|
%p= t('admin.instances.federation_policies.limited_federation_mode_description_html')
|
||||||
|
|
||||||
- if @instance.domain_allow
|
- if @instance.domain_allow
|
||||||
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
|
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
|
||||||
- else
|
- else
|
||||||
= link_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: 'button', method: :post
|
= link_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: 'button', method: :post
|
||||||
- else
|
- else
|
||||||
%p= t('admin.instances.content_policies.description_html')
|
%p= t('admin.instances.federation_policies.description_html')
|
||||||
|
|
||||||
- if @instance.domain_block
|
- if @instance.domain_block
|
||||||
.table-wrapper
|
.table-wrapper
|
||||||
%table.table.horizontal-table
|
%table.table.horizontal-table
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th{ width: '28%' }
|
||||||
|
%th
|
||||||
%tbody
|
%tbody
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.instances.content_policies.comment')
|
%th= t('admin.instances.federation_policies.comment')
|
||||||
%td= @instance.domain_block.private_comment
|
%td
|
||||||
|
- if @instance.domain_block.private_comment.present?
|
||||||
|
= @instance.domain_block.private_comment
|
||||||
|
- else
|
||||||
|
%em= t('admin.instances.federation_policies.no_comment')
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.instances.content_policies.reason')
|
%th= t('admin.instances.federation_policies.reason')
|
||||||
%td= @instance.domain_block.public_comment
|
%td
|
||||||
|
- if @instance.domain_block.public_comment.present?
|
||||||
|
= @instance.domain_block.public_comment
|
||||||
|
- else
|
||||||
|
%em= t('admin.instances.federation_policies.no_comment')
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.instances.content_policies.policy')
|
%th= t('admin.instances.federation_policies.policy')
|
||||||
%td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
|
%td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.federation_policies.policies') }.join(' · ')
|
||||||
|
|
||||||
|
.button-group
|
||||||
= link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
|
= link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
|
||||||
= link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
|
= link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button button-secondary', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
|
||||||
|
- if @instance.purgeable?
|
||||||
|
= link_to t('admin.instances.federation_policies.purge_data'), admin_instance_path(@instance), data: { confirm: t('admin.instances.confirm_purge'), method: :delete }, class: 'button button-tertiary button--destructive button-group__button--end'
|
||||||
- else
|
- else
|
||||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
|
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
|
||||||
|
|
||||||
|
@ -48,12 +67,13 @@
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
%h3= t('admin.instances.audit_log.title')
|
%h3= t('admin.instances.audit_log.title')
|
||||||
|
%p= t('admin.instances.audit_log.description')
|
||||||
- if @action_logs.empty?
|
- if @action_logs.empty?
|
||||||
%p= t('accounts.nothing_here')
|
%p= t('accounts.nothing_here')
|
||||||
- else
|
- else
|
||||||
.report-notes
|
.report-notes
|
||||||
= render partial: 'admin/action_logs/action_log', collection: @action_logs
|
= render partial: 'admin/action_logs/action_log', collection: @action_logs
|
||||||
= link_to t('admin.instances.audit_log.view_all'), admin_action_logs_path(target_domain: @instance.domain), class: 'button'
|
= link_to t('admin.instances.audit_log.view_all'), admin_action_logs_path(target_domain: @instance.domain), class: 'button button-secondary'
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
|
@ -71,35 +91,3 @@
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= form.button :button, t('admin.instances.moderation_notes.create'), type: :submit
|
= form.button :button, t('admin.instances.moderation_notes.create'), type: :submit
|
||||||
|
|
||||||
- if @instance.persisted?
|
|
||||||
%hr.spacer/
|
|
||||||
|
|
||||||
%h3= t('admin.instances.availability.title')
|
|
||||||
|
|
||||||
%p
|
|
||||||
= t('admin.instances.availability.description_html', count: DeliveryFailureTracker::FAILURE_DAYS_THRESHOLD)
|
|
||||||
|
|
||||||
.availability-indicator
|
|
||||||
%ul.availability-indicator__graphic
|
|
||||||
- @instance.availability_over_days(14).each do |(date, failing)|
|
|
||||||
%li.availability-indicator__graphic__item{ class: failing ? 'negative' : 'neutral', title: l(date) }
|
|
||||||
.availability-indicator__hint
|
|
||||||
- if @instance.unavailable?
|
|
||||||
%span.negative-hint
|
|
||||||
= t('admin.instances.availability.failure_threshold_reached', date: l(@instance.unavailable_domain.created_at.to_date))
|
|
||||||
= link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
|
|
||||||
- elsif @instance.exhausted_deliveries_days.empty?
|
|
||||||
%span.positive-hint
|
|
||||||
= t('admin.instances.availability.no_failures_recorded')
|
|
||||||
= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
|
|
||||||
- else
|
|
||||||
%span.negative-hint
|
|
||||||
= t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
|
|
||||||
%span= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
|
|
||||||
%span= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
|
|
||||||
|
|
||||||
- if @instance.purgeable?
|
|
||||||
%p= t('admin.instances.purge_description_html')
|
|
||||||
|
|
||||||
= link_to t('admin.instances.purge'), admin_instance_path(@instance), data: { confirm: t('admin.instances.confirm_purge'), method: :delete }, class: 'button button--destructive'
|
|
||||||
|
|
28
app/views/admin/instances/statistics.html.haml
Normal file
28
app/views/admin/instances/statistics.html.haml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= @instance.domain
|
||||||
|
|
||||||
|
- content_for :heading do
|
||||||
|
%h2= t('admin.instances.instance.title', domain: @instance.domain)
|
||||||
|
= render partial: 'admin/instances/shared/links', locals: { instance: @instance }
|
||||||
|
|
||||||
|
- if @instance.persisted?
|
||||||
|
%p.lead= t('admin.instances.statistics.subtitle', domain: @instance.domain)
|
||||||
|
.dashboard
|
||||||
|
.dashboard__item
|
||||||
|
= react_admin_component :dimension,
|
||||||
|
dimension: 'instance_accounts',
|
||||||
|
label: t('admin.instances.dashboard.instance_accounts_dimension'),
|
||||||
|
params: { domain: @instance.domain },
|
||||||
|
start_at: @time_period.first,
|
||||||
|
end_at: @time_period.last,
|
||||||
|
limit: 8
|
||||||
|
.dashboard__item
|
||||||
|
= react_admin_component :dimension,
|
||||||
|
dimension: 'instance_languages',
|
||||||
|
label: t('admin.instances.dashboard.instance_languages_dimension'),
|
||||||
|
params: { domain: @instance.domain },
|
||||||
|
start_at: @time_period.first,
|
||||||
|
end_at: @time_period.last,
|
||||||
|
limit: 8
|
||||||
|
- else
|
||||||
|
%p.lead= t('admin.instances.availability.unknown_instance')
|
|
@ -520,8 +520,9 @@ en:
|
||||||
unsuppress: Restore follow recommendation
|
unsuppress: Restore follow recommendation
|
||||||
instances:
|
instances:
|
||||||
audit_log:
|
audit_log:
|
||||||
title: Recent Audit Logs
|
description: These are the 5 most recent audit log entries relating to this instance.
|
||||||
view_all: View full audit logs
|
title: Audit Logs
|
||||||
|
view_all: View all audit logs
|
||||||
availability:
|
availability:
|
||||||
description_html:
|
description_html:
|
||||||
one: If delivering to the domain fails <strong>%{count} day</strong> without succeeding, no further delivery attempts will be made unless a delivery <em>from</em> the domain is received.
|
one: If delivering to the domain fails <strong>%{count} day</strong> without succeeding, no further delivery attempts will be made unless a delivery <em>from</em> the domain is received.
|
||||||
|
@ -531,25 +532,16 @@ en:
|
||||||
one: Failed attempt on %{count} day.
|
one: Failed attempt on %{count} day.
|
||||||
other: Failed attempts on %{count} different days.
|
other: Failed attempts on %{count} different days.
|
||||||
no_failures_recorded: No failures on record.
|
no_failures_recorded: No failures on record.
|
||||||
|
period_description: The bars shown are for delivery status over the last %{count} days.
|
||||||
|
subtitle: Instance availability measures how successfully we've been able to federate
|
||||||
title: Availability
|
title: Availability
|
||||||
|
unknown_instance: There is no data available for this instance as this server doesn't currently federate with it.
|
||||||
warning: The last attempt to connect to this server has been unsuccessful
|
warning: The last attempt to connect to this server has been unsuccessful
|
||||||
back_to_all: All
|
back_to_all: All
|
||||||
back_to_limited: Limited
|
back_to_limited: Limited
|
||||||
back_to_warning: Warning
|
back_to_warning: Warning
|
||||||
by_domain: Domain
|
by_domain: Domain
|
||||||
confirm_purge: Are you sure you want to permanently delete data from this domain?
|
confirm_purge: Are you sure you want to permanently delete data from this domain?
|
||||||
content_policies:
|
|
||||||
comment: Internal note
|
|
||||||
description_html: You can define content policies that will be applied to all accounts from this domain and any of its subdomains.
|
|
||||||
limited_federation_mode_description_html: You can chose whether to allow federation with this domain.
|
|
||||||
policies:
|
|
||||||
reject_media: Reject media
|
|
||||||
reject_reports: Reject reports
|
|
||||||
silence: Limit
|
|
||||||
suspend: Suspend
|
|
||||||
policy: Policy
|
|
||||||
reason: Public reason
|
|
||||||
title: Content policies
|
|
||||||
dashboard:
|
dashboard:
|
||||||
instance_accounts_dimension: Most followed accounts
|
instance_accounts_dimension: Most followed accounts
|
||||||
instance_accounts_measure: stored accounts
|
instance_accounts_measure: stored accounts
|
||||||
|
@ -571,6 +563,22 @@ en:
|
||||||
delivery_error_hint: If delivery is not possible for %{count} days, it will be automatically marked as undeliverable.
|
delivery_error_hint: If delivery is not possible for %{count} days, it will be automatically marked as undeliverable.
|
||||||
destroyed_msg: Data from %{domain} is now queued for imminent deletion.
|
destroyed_msg: Data from %{domain} is now queued for imminent deletion.
|
||||||
empty: No domains found.
|
empty: No domains found.
|
||||||
|
federation_policies:
|
||||||
|
comment: Private note
|
||||||
|
description_html: You can set a federation policy that will be applied to all accounts from this domain and any of its subdomains.
|
||||||
|
limited_federation_mode_description_html: You can chose whether to allow federation with this domain.
|
||||||
|
no_comment: None
|
||||||
|
policies:
|
||||||
|
reject_media: Reject media
|
||||||
|
reject_reports: Reject reports
|
||||||
|
silence: Limit
|
||||||
|
suspend: Suspend
|
||||||
|
policy: Policy
|
||||||
|
purge_data: Purge Data
|
||||||
|
reason: Public reason
|
||||||
|
title: Federation policy
|
||||||
|
instance:
|
||||||
|
title: Federation with %{domain}
|
||||||
known_accounts:
|
known_accounts:
|
||||||
one: "%{count} known account"
|
one: "%{count} known account"
|
||||||
other: "%{count} known accounts"
|
other: "%{count} known accounts"
|
||||||
|
@ -587,15 +595,19 @@ en:
|
||||||
title: Moderation Notes
|
title: Moderation Notes
|
||||||
private_comment: Private comment
|
private_comment: Private comment
|
||||||
public_comment: Public comment
|
public_comment: Public comment
|
||||||
purge: Purge
|
purge: Purge Data
|
||||||
purge_description_html: If you believe this domain is offline for good, you can delete all account records and associated data from this domain from your storage. This may take a while.
|
purge_description_html: If you believe this domain is offline for good, you can delete all account records and associated data from this domain from your storage. This may take a while.
|
||||||
|
purge_title: Purge Data
|
||||||
|
statistics:
|
||||||
|
subtitle: Below are a few statistics about %{domain}, that you may find interesting.
|
||||||
|
title: Statistics
|
||||||
title: Federation
|
title: Federation
|
||||||
total_blocked_by_us: Blocked by us
|
total_blocked_by_us: Blocked by us
|
||||||
total_followed_by_them: Followed by them
|
total_followed_by_them: Followed by them
|
||||||
total_followed_by_us: Followed by us
|
total_followed_by_us: Followed by us
|
||||||
total_reported: Reports about them
|
total_reported: Reports about them
|
||||||
total_storage: Media attachments
|
total_storage: Media attachments
|
||||||
totals_time_period_hint_html: The totals displayed below include data for all time.
|
totals_time_period_hint_html: The totals displayed above include data for all time.
|
||||||
unknown_instance: There is currently no record of this domain on this server.
|
unknown_instance: There is currently no record of this domain on this server.
|
||||||
invites:
|
invites:
|
||||||
deactivate_all: Deactivate all
|
deactivate_all: Deactivate all
|
||||||
|
|
|
@ -87,6 +87,8 @@ namespace :admin do
|
||||||
|
|
||||||
resources :instances, only: [:index, :show, :destroy], constraints: { id: %r{[^/]+} }, format: 'html' do
|
resources :instances, only: [:index, :show, :destroy], constraints: { id: %r{[^/]+} }, format: 'html' do
|
||||||
member do
|
member do
|
||||||
|
get :availability
|
||||||
|
get :statistics
|
||||||
post :clear_delivery_errors
|
post :clear_delivery_errors
|
||||||
post :restart_delivery
|
post :restart_delivery
|
||||||
post :stop_delivery
|
post :stop_delivery
|
||||||
|
|
Loading…
Reference in New Issue
Block a user