From b09cdd4b7bde2b6353e0a1289b9380bbe8f75af1 Mon Sep 17 00:00:00 2001 From: Borislav Pantaleev Date: Tue, 17 Sep 2024 09:23:16 +0300 Subject: [PATCH 1/9] wip --- src/resources/users.tsx | 79 +++++++++++++++++++++---------------- src/synapse/dataProvider.ts | 70 ++++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 36 deletions(-) diff --git a/src/resources/users.tsx b/src/resources/users.tsx index 68eb04b..b2271f2 100644 --- a/src/resources/users.tsx +++ b/src/resources/users.tsx @@ -55,6 +55,8 @@ import { ToolbarClasses, Identifier, RaRecord, + ImageInput, + ImageField, } from "react-admin"; import { Link } from "react-router-dom"; @@ -101,26 +103,24 @@ const userFilters = [ , ]; -const UserPreventSelfDelete: React.FC<{ children: React.ReactNode, ownUserIsSelected: boolean }> = (props) => { +const UserPreventSelfDelete: React.FC<{ children: React.ReactNode; ownUserIsSelected: boolean }> = props => { const ownUserIsSelected = props.ownUserIsSelected; const notify = useNotify(); const translate = useTranslate(); const handleDeleteClick = (ev: React.MouseEvent) => { if (ownUserIsSelected) { - notify({translate("resources.users.helper.erase_admin_error")}) + notify({translate("resources.users.helper.erase_admin_error")}); ev.stopPropagation(); } }; - return
- {props.children} -
+ return
{props.children}
; }; const UserBulkActionButtons = () => { const record = useListContext(); - const [ ownUserIsSelected, setOwnUserIsSelected ] = useState(false); + const [ownUserIsSelected, setOwnUserIsSelected] = useState(false); const selectedIds = record.selectedIds; const ownUserId = localStorage.getItem("user_id"); const notify = useNotify(); @@ -128,19 +128,20 @@ const UserBulkActionButtons = () => { useEffect(() => { setOwnUserIsSelected(selectedIds.includes(ownUserId)); - }, [ selectedIds ]); + }, [selectedIds]); - - return <> - - - - - + return ( + <> + + + + + + ); }; const usersRowClick = (id: Identifier, resource: string, record: RaRecord): string => { @@ -204,9 +205,12 @@ const UserEditActions = () => { }; export const UserCreate = (props: CreateProps) => ( - { - return `users/${id}`; - }}> + { + return `users/${id}`; + }} + > @@ -237,7 +241,7 @@ const UserTitle = () => { {translate("resources.users.name", { smart_count: 1, })}{" "} - {record ? ( record.displayname ? `"${record.displayname}"` : `"${record.name}"`) : ""} + {record ? (record.displayname ? `"${record.displayname}"` : `"${record.name}"`) : ""} ); }; @@ -250,41 +254,50 @@ const UserEditToolbar = () => { ownUserIsSelected = record.id === ownUserId; } - return <> -
- + return ( + <> +
+ - -
- +
+
+ + ); }; -const UserBooleanInput = (props) => { +const UserBooleanInput = props => { const record = useRecordContext(); const ownUserId = localStorage.getItem("user_id"); const isOwnUser = false; let ownUserIsSelected = false; - if (record && (record.id === ownUserId)) { + if (record && record.id === ownUserId) { ownUserIsSelected = true; } - return -} + return ( + + + + ); +}; export const UserEdit = (props: EditProps) => { const translate = useTranslate(); return ( - } actions={}> + } actions={} mutationMode="pessimistic"> }> }> + + + diff --git a/src/synapse/dataProvider.ts b/src/synapse/dataProvider.ts index d69a760..5f2efee 100644 --- a/src/synapse/dataProvider.ts +++ b/src/synapse/dataProvider.ts @@ -1,6 +1,6 @@ import { stringify } from "query-string"; -import { DataProvider, DeleteParams, HttpError, Identifier, Options, RaRecord, fetchUtils } from "react-admin"; +import { DataProvider, DeleteParams, HttpError, Identifier, Options, RaRecord, fetchUtils, withLifecycleCallbacks } from "react-admin"; import storage from "../storage"; import { MatrixError, displayError } from "../components/error"; @@ -9,7 +9,7 @@ import { MatrixError, displayError } from "../components/error"; const jsonClient = async (url: string, options: Options = {}) => { const token = storage.getItem("access_token"); console.log("httpClient " + url); - if (token != null) { + if (!options.user && token !== null) { options.user = { authenticated: true, token: `Bearer ${token}`, @@ -29,6 +29,7 @@ const jsonClient = async (url: string, options: Options = {}) => { }; const mxcUrlToHttp = (mxcUrl: string) => { + console.log("@mxcUrlToHttp", mxcUrl); const homeserver = storage.getItem("base_url"); const re = /^mxc:\/\/([^/]+)\/(\w+)/; const ret = re.exec(mxcUrl); @@ -225,8 +226,20 @@ export interface DeleteMediaResult { total: number; } +export interface UploadMediaParams { + user_id: string; + file: File; + filename: string; + content_type: string; +} + +export interface UploadMediaResult { + content_uri: string; +} + export interface SynapseDataProvider extends DataProvider { deleteMedia: (params: DeleteMediaParams) => Promise; + uploadMedia: (params: UploadMediaParams) => Promise; } const resourceMap = { @@ -499,7 +512,7 @@ function getSearchOrder(order: "ASC" | "DESC") { } } -const dataProvider: SynapseDataProvider = { +const baseDataProvider: SynapseDataProvider = { getList: async (resource, params) => { console.log("getList " + resource); const { user_id, name, guests, deactivated, locked, search_term, destination, valid } = params.filter; @@ -741,6 +754,57 @@ const dataProvider: SynapseDataProvider = { const { json } = await jsonClient(endpoint_url, { method: "POST" }); return json as DeleteMediaResult; }, + + uploadMedia: async ({ user_id, file, filename, content_type }) => { + const base_url = storage.getItem("base_url"); + const loginURL = `${base_url}/_synapse/admin/v1/users/${user_id}/login` + const uploadMediaURL = `${base_url}/_matrix/media/v3/upload` + const { json: loginJson } = await jsonClient(loginURL, { + method: "POST", + }); + console.log("loginJson", loginJson); + if (!loginJson.access_token) { + throw Error("Failed to obtain access token."); + } + + const formData = new FormData(); + formData.append("file", file); + const { json } = await jsonClient(`${uploadMediaURL}?filename="${filename}"`, { + method: "POST", + headers: new Headers({ + Accept: 'application/json', + "Content-Type": content_type + }) as Headers, + user: { + authenticated: true, + token: `Bearer ${loginJson.access_token}`, + }, + body: formData + }); + return json as UploadMediaResult; + } }; +const dataProvider = withLifecycleCallbacks(baseDataProvider, [ + { + resource: "users", + beforeUpdate: async (params: any, dataProvider: DataProvider) => { + console.log("beforeUpdate " + JSON.stringify(params)); + const avatar_file = params.data.avatar_file.rawFile; + + if (avatar_file instanceof File) { + const reponse = await dataProvider.uploadMedia({ + user_id: params.id, + file: params.data.avatar_file.rawFile, + filename: params.data.avatar_file.title, + content_type: params.data.avatar_file.rawFile.type + }); + params.data.avatar_url = reponse.content_uri; + } + return params; + }, + }, +]); + + export default dataProvider; From 0f509d5ffd22a67294645d328334dd24173efe71 Mon Sep 17 00:00:00 2001 From: Aine Date: Tue, 17 Sep 2024 10:05:52 +0300 Subject: [PATCH 2/9] some fixes --- src/synapse/dataProvider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/synapse/dataProvider.ts b/src/synapse/dataProvider.ts index 5f2efee..fe2fcbe 100644 --- a/src/synapse/dataProvider.ts +++ b/src/synapse/dataProvider.ts @@ -767,9 +767,7 @@ const baseDataProvider: SynapseDataProvider = { throw Error("Failed to obtain access token."); } - const formData = new FormData(); - formData.append("file", file); - const { json } = await jsonClient(`${uploadMediaURL}?filename="${filename}"`, { + const { json } = await jsonClient(`${uploadMediaURL}?filename=${filename}`, { method: "POST", headers: new Headers({ Accept: 'application/json', @@ -779,7 +777,7 @@ const baseDataProvider: SynapseDataProvider = { authenticated: true, token: `Bearer ${loginJson.access_token}`, }, - body: formData + body: file }); return json as UploadMediaResult; } From 5b7d7f8124db3da81c61486dd9aef74b91f6fdea Mon Sep 17 00:00:00 2001 From: Aine Date: Tue, 17 Sep 2024 10:16:12 +0300 Subject: [PATCH 3/9] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fc90ac3..f4f71b4 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ The following changes are already implemented: * [Put the version into manifest.json](https://github.com/Awesome-Technologies/synapse-admin/issues/507) (CI only) * [Federation page improvements](https://github.com/Awesome-Technologies/synapse-admin/pull/583) (using theme colors) * [Add UI option to block deleted rooms from being rejoined](https://github.com/etkecc/synapse-admin/pull/26) +* [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27) _the list will be updated as new changes are added_ From ce34071dbe09c0204a40d02524cee2ea72c7b8eb Mon Sep 17 00:00:00 2001 From: Aine Date: Tue, 17 Sep 2024 10:16:12 +0300 Subject: [PATCH 4/9] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e800c4a..dca7865 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The following changes are already implemented: * [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27) * [Fix required fields check on Bulk registration CSV upload](https://github.com/etkecc/synapse-admin/pull/32) * [Fix requests with invalid MXIDs on Bulk registration](https://github.com/etkecc/synapse-admin/pull/33) +* [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27) _the list will be updated as new changes are added_ From 6b27484db4aa8c0b01f38c82e0e7c4e68c882131 Mon Sep 17 00:00:00 2001 From: Borislav Pantaleev Date: Tue, 17 Sep 2024 22:49:00 +0300 Subject: [PATCH 5/9] Add option to change/erase any user's avatar --- src/i18n/de.ts | 1 + src/i18n/en.ts | 1 + src/i18n/fr.ts | 1 + src/i18n/index.d.ts | 1 + src/i18n/ru.ts | 1 + src/i18n/zh.ts | 1 + src/resources/users.tsx | 10 ++--- src/synapse/authProvider.test.ts | 2 +- src/synapse/dataProvider.test.ts | 12 +++--- src/synapse/dataProvider.ts | 64 ++++++++++++++++---------------- src/synapse/synapse.ts | 8 ++-- 11 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/i18n/de.ts b/src/i18n/de.ts index d5034f8..a6d9c73 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -147,6 +147,7 @@ const de: SynapseTranslationMessages = { }, action: { erase: "Lösche Benutzerdaten", + erase_avatar: "Avatar löschen" }, }, rooms: { diff --git a/src/i18n/en.ts b/src/i18n/en.ts index a44d405..c3de63c 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -146,6 +146,7 @@ const en: SynapseTranslationMessages = { }, action: { erase: "Erase user data", + erase_avatar: "Erase avatar" }, }, rooms: { diff --git a/src/i18n/fr.ts b/src/i18n/fr.ts index 50ac6d0..0506243 100644 --- a/src/i18n/fr.ts +++ b/src/i18n/fr.ts @@ -144,6 +144,7 @@ const fr: SynapseTranslationMessages = { }, action: { erase: "Effacer les données de l'utilisateur", + erase_avatar: "Effacer l'avatar", }, }, rooms: { diff --git a/src/i18n/index.d.ts b/src/i18n/index.d.ts index 325fe21..abef700 100644 --- a/src/i18n/index.d.ts +++ b/src/i18n/index.d.ts @@ -142,6 +142,7 @@ interface SynapseTranslationMessages extends TranslationMessages { }; action: { erase: string; + erase_avatar: string; }; }; rooms: { diff --git a/src/i18n/ru.ts b/src/i18n/ru.ts index d5380ec..cc26253 100644 --- a/src/i18n/ru.ts +++ b/src/i18n/ru.ts @@ -155,6 +155,7 @@ const ru: SynapseTranslationMessages = { }, action: { erase: "Удалить данные пользователя", + erase_avatar: "Удалить аватар", }, }, rooms: { diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index 61a59cb..5936830 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -139,6 +139,7 @@ const zh: SynapseTranslationMessages = { }, action: { erase: "抹除用户信息", + erase_avatar: "抹掉头像", }, }, rooms: { diff --git a/src/resources/users.tsx b/src/resources/users.tsx index b2271f2..cbc4a38 100644 --- a/src/resources/users.tsx +++ b/src/resources/users.tsx @@ -271,7 +271,6 @@ const UserEditToolbar = () => { const UserBooleanInput = props => { const record = useRecordContext(); const ownUserId = localStorage.getItem("user_id"); - const isOwnUser = false; let ownUserIsSelected = false; if (record && record.id === ownUserId) { ownUserIsSelected = true; @@ -291,13 +290,14 @@ export const UserEdit = (props: EditProps) => { } actions={} mutationMode="pessimistic"> }> }> - + + + + + - - - diff --git a/src/synapse/authProvider.test.ts b/src/synapse/authProvider.test.ts index 340f5c3..3dddc74 100644 --- a/src/synapse/authProvider.test.ts +++ b/src/synapse/authProvider.test.ts @@ -101,7 +101,7 @@ describe("authProvider", () => { }); it("should reject if error.status is 401", async () => { - await expect(authProvider.checkError({ status: 401 })).rejects.toBeUndefined(); + await expect(authProvider.checkError(new HttpError("test-error", 401, {errcode: "test-errcode", error: "test-error"}))).rejects.toBeDefined(); }); it("should reject if error.status is 403", async () => { diff --git a/src/synapse/dataProvider.test.ts b/src/synapse/dataProvider.test.ts index a578674..dbf9b45 100644 --- a/src/synapse/dataProvider.test.ts +++ b/src/synapse/dataProvider.test.ts @@ -18,7 +18,7 @@ describe("dataProvider", () => { JSON.stringify({ users: [ { - name: "user_id1", + name: "@user_id1:provider", password_hash: "password_hash1", is_guest: 0, admin: 0, @@ -27,7 +27,7 @@ describe("dataProvider", () => { displayname: "User One", }, { - name: "user_id2", + name: "@user_id2:provider", password_hash: "password_hash2", is_guest: 0, admin: 1, @@ -47,7 +47,7 @@ describe("dataProvider", () => { filter: { author_id: 12 }, }); - expect(users.data[0].id).toEqual("user_id1"); + expect(users.data[0].id).toEqual("@user_id1:provider"); expect(users.total).toEqual(200); expect(fetch).toHaveBeenCalledTimes(1); }); @@ -55,7 +55,7 @@ describe("dataProvider", () => { it("fetches one user", async () => { fetchMock.mockResponseOnce( JSON.stringify({ - name: "user_id1", + name: "@user_id1:provider", password: "user_password", displayname: "User", threepids: [ @@ -74,9 +74,9 @@ describe("dataProvider", () => { }) ); - const user = await dataProvider.getOne("users", { id: "user_id1" }); + const user = await dataProvider.getOne("users", { id: "@user_id1:provider" }); - expect(user.data.id).toEqual("user_id1"); + expect(user.data.id).toEqual("@user_id1:provider"); expect(user.data.displayname).toEqual("User"); expect(fetch).toHaveBeenCalledTimes(1); }); diff --git a/src/synapse/dataProvider.ts b/src/synapse/dataProvider.ts index 502de40..dcbd14c 100644 --- a/src/synapse/dataProvider.ts +++ b/src/synapse/dataProvider.ts @@ -1,16 +1,26 @@ import { stringify } from "query-string"; -import { DataProvider, DeleteParams, HttpError, Identifier, Options, RaRecord, fetchUtils, withLifecycleCallbacks } from "react-admin"; +import { + DataProvider, + DeleteParams, + HttpError, + Identifier, + Options, + RaRecord, + UpdateParams, + fetchUtils, + withLifecycleCallbacks, +} from "react-admin"; import storage from "../storage"; -import { returnMXID } from "./synapse.ts" +import { returnMXID } from "./synapse"; import { MatrixError, displayError } from "../components/error"; // Adds the access token to all requests const jsonClient = async (url: string, options: Options = {}) => { const token = storage.getItem("access_token"); console.log("httpClient " + url); - if (!options.user && token !== null) { + if (token !== null) { options.user = { authenticated: true, token: `Bearer ${token}`, @@ -23,7 +33,9 @@ const jsonClient = async (url: string, options: Options = {}) => { const error = err as HttpError; const errorStatus = error.status; const errorBody = error.body as MatrixError; - const errMsg = !!errorBody?.errcode ? displayError(errorBody.errcode, errorStatus, errorBody.error) : displayError("M_INVALID", errorStatus, error.message); + const errMsg = !!errorBody?.errcode + ? displayError(errorBody.errcode, errorStatus, errorBody.error) + : displayError("M_INVALID", errorStatus, error.message); return Promise.reject(new HttpError(errMsg, errorStatus, errorBody)); } @@ -228,7 +240,6 @@ export interface DeleteMediaResult { } export interface UploadMediaParams { - user_id: string; file: File; filename: string; content_type: string; @@ -756,47 +767,39 @@ const baseDataProvider: SynapseDataProvider = { return json as DeleteMediaResult; }, - uploadMedia: async ({ user_id, file, filename, content_type }) => { + uploadMedia: async ({ file, filename, content_type }: UploadMediaParams) => { const base_url = storage.getItem("base_url"); - const loginURL = `${base_url}/_synapse/admin/v1/users/${user_id}/login` - const uploadMediaURL = `${base_url}/_matrix/media/v3/upload` - const { json: loginJson } = await jsonClient(loginURL, { - method: "POST", - }); - console.log("loginJson", loginJson); - if (!loginJson.access_token) { - throw Error("Failed to obtain access token."); - } + const uploadMediaURL = `${base_url}/_matrix/media/v3/upload`; const { json } = await jsonClient(`${uploadMediaURL}?filename=${filename}`, { method: "POST", + body: file, headers: new Headers({ - Accept: 'application/json', - "Content-Type": content_type + Accept: "application/json", + "Content-Type": content_type, }) as Headers, - user: { - authenticated: true, - token: `Bearer ${loginJson.access_token}`, - }, - body: file }); return json as UploadMediaResult; - } + }, }; const dataProvider = withLifecycleCallbacks(baseDataProvider, [ { resource: "users", - beforeUpdate: async (params: any, dataProvider: DataProvider) => { - console.log("beforeUpdate " + JSON.stringify(params)); - const avatar_file = params.data.avatar_file.rawFile; + beforeUpdate: async (params: UpdateParams, dataProvider: DataProvider) => { + const avatarFile = params.data.avatar_file?.rawFile; + const avatarErase = params.data.avatar_erase; - if (avatar_file instanceof File) { + if (avatarErase) { + params.data.avatar_url = ""; + return params; + } + + if (avatarFile instanceof File) { const reponse = await dataProvider.uploadMedia({ - user_id: params.id, - file: params.data.avatar_file.rawFile, + file: avatarFile, filename: params.data.avatar_file.title, - content_type: params.data.avatar_file.rawFile.type + content_type: params.data.avatar_file.rawFile.type, }); params.data.avatar_url = reponse.content_uri; } @@ -805,5 +808,4 @@ const dataProvider = withLifecycleCallbacks(baseDataProvider, [ }, ]); - export default dataProvider; diff --git a/src/synapse/synapse.ts b/src/synapse/synapse.ts index 13c18f3..2e49501 100644 --- a/src/synapse/synapse.ts +++ b/src/synapse/synapse.ts @@ -1,4 +1,4 @@ -import { fetchUtils } from "react-admin"; +import { Identifier, fetchUtils } from "react-admin"; import storage from "../storage"; @@ -77,17 +77,17 @@ export function generateRandomMxId(): string { * @param input the input string * @returns full MXID as string */ -export function returnMXID(input: string): string { +export function returnMXID(input: string | Identifier): string { const homeserver = storage.getItem("home_server"); // Check if the input already looks like a valid MXID (i.e., starts with "@" and contains ":") const mxidPattern = /^@[^@:]+:[^@:]+$/; - if (mxidPattern.test(input)) { + if (typeof input === 'string' && mxidPattern.test(input)) { return input; // Already a valid MXID } // If input is not a valid MXID, assume it's a localpart and construct the MXID - const localpart = input.startsWith('@') ? input.slice(1) : input; + const localpart = typeof input === 'string' && input.startsWith('@') ? input.slice(1) : input; return `@${localpart}:${homeserver}`; } From 0bddffdea409db479d776c13aeae3873a1d7f9ec Mon Sep 17 00:00:00 2001 From: Borislav Pantaleev Date: Tue, 17 Sep 2024 22:49:45 +0300 Subject: [PATCH 6/9] Fix README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index dca7865..4d03dec 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ The following changes are already implemented: * [Put the version into manifest.json](https://github.com/Awesome-Technologies/synapse-admin/issues/507) (CI only) * [Federation page improvements](https://github.com/Awesome-Technologies/synapse-admin/pull/583) (using theme colors) * [Add UI option to block deleted rooms from being rejoined](https://github.com/etkecc/synapse-admin/pull/26) -* [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27) * [Fix required fields check on Bulk registration CSV upload](https://github.com/etkecc/synapse-admin/pull/32) * [Fix requests with invalid MXIDs on Bulk registration](https://github.com/etkecc/synapse-admin/pull/33) * [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27) From 3754f51d6a53da2d70a9bfb6d5af92d375b2bdfb Mon Sep 17 00:00:00 2001 From: Borislav Pantaleev Date: Tue, 17 Sep 2024 22:50:33 +0300 Subject: [PATCH 7/9] Remove mutationMode from Edit --- src/resources/users.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/users.tsx b/src/resources/users.tsx index cbc4a38..b1086d8 100644 --- a/src/resources/users.tsx +++ b/src/resources/users.tsx @@ -287,7 +287,7 @@ export const UserEdit = (props: EditProps) => { const translate = useTranslate(); return ( - } actions={} mutationMode="pessimistic"> + } actions={}> }> }> From d5b9cf8eedff40c007b7d82f911f3ff1f81dbf34 Mon Sep 17 00:00:00 2001 From: Borislav Pantaleev Date: Tue, 17 Sep 2024 22:51:32 +0300 Subject: [PATCH 8/9] remove log --- src/synapse/dataProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/synapse/dataProvider.ts b/src/synapse/dataProvider.ts index dcbd14c..fda55d1 100644 --- a/src/synapse/dataProvider.ts +++ b/src/synapse/dataProvider.ts @@ -42,7 +42,6 @@ const jsonClient = async (url: string, options: Options = {}) => { }; const mxcUrlToHttp = (mxcUrl: string) => { - console.log("@mxcUrlToHttp", mxcUrl); const homeserver = storage.getItem("base_url"); const re = /^mxc:\/\/([^/]+)\/(\w+)/; const ret = re.exec(mxcUrl); From 5a15362ed518a48f110530b142bee097a583bd13 Mon Sep 17 00:00:00 2001 From: Aine Date: Tue, 17 Sep 2024 23:05:34 +0300 Subject: [PATCH 9/9] update readme --- .github/CONTRIBUTING.md | 16 ---------------- README.md | 17 +++++------------ 2 files changed, 5 insertions(+), 28 deletions(-) delete mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 5bcd606..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,16 +0,0 @@ -# Contributing to [etkecc/synapse-admin](https://github.com/etkecc/synapse-admin) - -While etke.cc fork is intended to accept more QoL changes and features, -it's good idea to open PR into the upstream repo: [Awesome-Technologies/Synapse-Admin](https://github.com/Awesome-Technologies/synapse-admin). - -1. Use the etkecc/synapse-admin **master** branch as your branch upstream: `git checkout master; git pull; git checkout -b my-new-feature` -2. Once your changes are ready, please, open **2** PRs: one from your branch to `Awesome-Technologies/Synapse-Admin` **master**, and another one to `etkecc/synapse-admin` **main** -3. Once PR is accepted in the `etkecc/synapse-admin`, update `README.md` file (either directly in the `main` branch, or via another PR) to add link to the merged PR in the [Fork differences](https://github.com/etkecc/synapse-admin#fork-differences) section - -### Why? - -The upstream project may not want to accept all the changes, so to ensure they are not lost, we will gladly add them to the etke.cc fork. -Unfortunately, it's challenging to keep changes separated, so to avoid messing upstream and fork changes (e.g., CI changes that should not be pushed to the upstream, as they intended for this fork specifically), there are 2 branches: - -* `master` - read-only copy of upstream's master branch to easily sync changes, and use it as base for new PRs -* `main` - fork-own branch with all changes diff --git a/README.md b/README.md index 4d03dec..a0c1be2 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,19 @@ -[![GitHub license](https://img.shields.io/github/license/Awesome-Technologies/synapse-admin)](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE) -[![Build Status](https://api.travis-ci.com/Awesome-Technologies/synapse-admin.svg?branch=master)](https://app.travis-ci.com/github/Awesome-Technologies/synapse-admin) -[![build-test](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml) -[![gh-pages](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/edge_ghpage.yml/badge.svg)](https://awesome-technologies.github.io/synapse-admin/) -[![docker-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/docker-release.yml/badge.svg)](https://hub.docker.com/r/awesometechnologies/synapse-admin) -[![github-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/github-release.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/releases) - -# Synapse admin ui +# Synapse Admin UI [![GitHub license](https://img.shields.io/github/license/Awesome-Technologies/synapse-admin)](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE) This project is built using [react-admin](https://marmelab.com/react-admin/). ## Fork differences +With [Awesome-Technologies/synapse-admin](https://github.com/Awesome-Technologies/synapse-admin) as the upstream, this +fork is intended to be a more feature-rich version of the original project. The main goal is to provide a more +user-friendly interface for managing Synapse homeservers. + ### Available via CDN On [admin.etke.cc](https://admin.etke.cc) you can find the latest version of this fork. ### Changes -With [Awesome-Technologies/synapse-admin](https://github.com/Awesome-Technologies/synapse-admin) as the upstream, this -fork is intended to be a more feature-rich version of the original project. The main goal is to provide a more -user-friendly interface for managing Synapse homeservers. - The following changes are already implemented: * [Prevent admins from deleting themselves](https://github.com/etkecc/synapse-admin/pull/1)