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)?;
pub online: usize,
version: String,
my_user: Option<User_>,
+ federated_instances: Vec<String>,
}
#[derive(Serialize, Deserialize)]
online,
version: version::VERSION.to_string(),
my_user,
+ federated_instances: Settings::get().get_allowed_instances(),
})
}
}
online: 0,
version: version::VERSION.to_string(),
my_user: Some(user),
+ federated_instances: Settings::get().get_allowed_instances(),
})
}
}
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();
)
.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> {
banned: [],
online: null,
version: null,
+ federated_instances: null,
},
siteConfigForm: {
config_hjson: null,
{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')}
--- /dev/null
+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);
+ }
+ }
+}
banned: [],
online: null,
version: null,
+ federated_instances: null,
},
showEditSite: false,
loading: true,
banned: [],
online: null,
version: null,
+ federated_instances: null,
},
searchParam: '',
toggleSearch: false,
},
online: null,
version: null,
+ federated_instances: undefined,
},
};
},
version: undefined,
my_user: undefined,
+ federated_instances: undefined,
},
};
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';
path={`/password_change/:token`}
component={PasswordChange}
/>
+ <Route path={`/instances`} component={Instances} />
</Switch>
<Symbols />
</div>
online: number;
version: string;
my_user?: User;
+ federated_instances: Array<string>;
}
export interface SiteResponse {
"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."
}