Add a simple linked instances page. Fixes #1070 (#1071)

* Add a simple linked instances page. Fixes #1070

* Changing allowed_instances to federated_instances.
This commit is contained in:
Dessalines 2020-08-11 10:58:32 -04:00 committed by GitHub
parent 3da47352be
commit bc523abd62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 136 additions and 8 deletions

View file

@ -133,6 +133,20 @@ impl Settings {
fs::read_to_string(CONFIG_FILE)
}
pub fn get_allowed_instances(&self) -> Vec<String> {
let mut allowed_instances: Vec<String> = self
.federation
.allowed_instances
.split(',')
.map(|d| d.to_string())
.collect();
// The defaults.hjson config always returns a [""]
allowed_instances.retain(|d| !d.eq(""));
allowed_instances
}
pub fn save_config_file(data: &str) -> Result<String, Error> {
fs::write(CONFIG_FILE, data)?;

View file

@ -130,6 +130,7 @@ pub struct GetSiteResponse {
pub online: usize,
version: String,
my_user: Option<User_>,
federated_instances: Vec<String>,
}
#[derive(Serialize, Deserialize)]
@ -433,6 +434,7 @@ impl Perform for Oper<GetSite> {
online,
version: version::VERSION.to_string(),
my_user,
federated_instances: Settings::get().get_allowed_instances(),
})
}
}
@ -659,6 +661,7 @@ impl Perform for Oper<TransferSite> {
online: 0,
version: version::VERSION.to_string(),
my_user: Some(user),
federated_instances: Settings::get().get_allowed_instances(),
})
}
}

View file

@ -76,12 +76,8 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into());
}
let mut allowed_instances: Vec<String> = Settings::get()
.federation
.allowed_instances
.split(',')
.map(|d| d.to_string())
.collect();
let mut allowed_instances: Vec<String> = Settings::get().get_allowed_instances();
// need to allow this explicitly because apub activities might contain objects from our local
// instance. replace is needed to remove the port in our federation test setup.
let settings = Settings::get();

View file

@ -40,7 +40,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
)
.route("/search", web::get().to(index))
.route("/sponsors", web::get().to(index))
.route("/password_change/{token}", web::get().to(index));
.route("/password_change/{token}", web::get().to(index))
.route("/instances", web::get().to(index));
}
async fn index() -> Result<NamedFile, Error> {

View file

@ -48,6 +48,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
banned: [],
online: null,
version: null,
federated_instances: null,
},
siteConfigForm: {
config_hjson: null,

View file

@ -51,6 +51,11 @@ export class Footer extends Component<any, FooterState> {
{i18n.t('modlog')}
</Link>
</li>
<li class="nav-item">
<Link class="nav-link" to="/instances">
{i18n.t('instances')}
</Link>
</li>
<li class="nav-item">
<a class="nav-link" href={'/docs/index.html'}>
{i18n.t('docs')}

98
ui/src/components/instances.tsx vendored Normal file
View file

@ -0,0 +1,98 @@
import { Component } from 'inferno';
import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
UserOperation,
WebSocketJsonResponse,
GetSiteResponse,
} from '../interfaces';
import { WebSocketService } from '../services';
import { wsJsonToRes, toast } from '../utils';
import { i18n } from '../i18next';
interface InstancesState {
loading: boolean;
siteRes: GetSiteResponse;
}
export class Instances extends Component<any, InstancesState> {
private subscription: Subscription;
private emptyState: InstancesState = {
loading: true,
siteRes: undefined,
};
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
msg => this.parseMessage(msg),
err => console.error(err),
() => console.log('complete')
);
WebSocketService.Instance.getSite();
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
get documentTitle(): string {
if (this.state.siteRes) {
return `${i18n.t('instances')} - ${this.state.siteRes.site.name}`;
} else {
return 'Lemmy';
}
}
render() {
return (
<div class="container">
<Helmet title={this.documentTitle} />
{this.state.loading ? (
<h5 class="">
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5>
) : (
<div>
<h5>{i18n.t('linked_instances')}</h5>
{this.state.siteRes &&
this.state.siteRes.federated_instances.length ? (
<ul>
{this.state.siteRes.federated_instances.map(i => (
<li>
<a href={`https://${i}`} target="_blank" rel="noopener">
{i}
</a>
</li>
))}
</ul>
) : (
<div>{i18n.t('none_found')}</div>
)}
</div>
)}
</div>
);
}
parseMessage(msg: WebSocketJsonResponse) {
console.log(msg);
let res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
return;
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
this.state.siteRes = data;
this.state.loading = false;
this.setState(this.state);
}
}
}

View file

@ -114,6 +114,7 @@ export class Main extends Component<any, MainState> {
banned: [],
online: null,
version: null,
federated_instances: null,
},
showEditSite: false,
loading: true,

View file

@ -82,6 +82,7 @@ export class Navbar extends Component<any, NavbarState> {
banned: [],
online: null,
version: null,
federated_instances: null,
},
searchParam: '',
toggleSearch: false,

View file

@ -97,6 +97,7 @@ export class Post extends Component<any, PostState> {
},
online: null,
version: null,
federated_instances: undefined,
},
};

View file

@ -142,6 +142,7 @@ export class User extends Component<any, UserState> {
},
version: undefined,
my_user: undefined,
federated_instances: undefined,
},
};

2
ui/src/index.tsx vendored
View file

@ -19,6 +19,7 @@ import { AdminSettings } from './components/admin-settings';
import { Inbox } from './components/inbox';
import { Search } from './components/search';
import { Sponsors } from './components/sponsors';
import { Instances } from './components/instances';
import { Symbols } from './components/symbols';
import { i18n } from './i18next';
@ -89,6 +90,7 @@ class Index extends Component<any, any> {
path={`/password_change/:token`}
component={PasswordChange}
/>
<Route path={`/instances`} component={Instances} />
</Switch>
<Symbols />
</div>

View file

@ -867,6 +867,7 @@ export interface GetSiteResponse {
online: number;
version: string;
my_user?: User;
federated_instances: Array<string>;
}
export interface SiteResponse {

View file

@ -294,5 +294,8 @@
"invalid_post_title": "Invalid post title",
"invalid_url": "Invalid URL.",
"play_captcha_audio": "Play Captcha Audio",
"bio": "Bio"
"bio": "Bio",
"instances": "Instances",
"linked_instances": "Linked Instances",
"none_found": "None found."
}