-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ab1edac
commit decff62
Showing
10 changed files
with
375 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
-- SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]> | ||
-- SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
-- | ||
-- SPDX-License-Identifier: Apache-2.0 | ||
-- SPDX-License-Identifier: EUPL-1.2 | ||
|
||
CREATE TABLE global_announcement ( | ||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY, | ||
text VARCHAR(200), | ||
enabled BOOLEAN DEFAULT FALSE NOT NULL, | ||
created_at TIMESTAMPTZ NOT NULL, | ||
updated_at TIMESTAMPTZ NOT NULL | ||
); | ||
|
||
CREATE FUNCTION sanitise_insert_global_announcement () RETURNS TRIGGER LANGUAGE plpgsql AS | ||
$$ | ||
BEGIN | ||
NEW.id = gen_random_uuid(); | ||
NEW.created_at = LOCALTIMESTAMP; | ||
NEW.updated_at = NEW.created_at; | ||
return NEW; | ||
END | ||
$$; | ||
|
||
CREATE TRIGGER sanitise_insert_global_announcement BEFORE INSERT ON global_announcement FOR EACH ROW EXECUTE PROCEDURE sanitise_insert_global_announcement(); | ||
|
||
CREATE FUNCTION sanitise_update_global_announcement() RETURNS TRIGGER LANGUAGE plpgsql AS | ||
$$ | ||
BEGIN | ||
NEW.id = OLD.id; | ||
NEW.created_at = OLD.created_at; | ||
NEW.updated_at = LOCALTIMESTAMP; | ||
return NEW; | ||
END | ||
$$; | ||
|
||
CREATE TRIGGER sanitise_update_global_announcement BEFORE UPDATE ON global_announcement FOR EACH ROW EXECUTE PROCEDURE sanitise_update_global_announcement(); | ||
|
||
ALTER TABLE global_announcement ENABLE ROW LEVEL SECURITY; | ||
|
||
CREATE POLICY anyone_can_read ON global_announcement FOR SELECT TO rsd_web_anon, rsd_user USING (TRUE);´ | ||
|
||
CREATE POLICY admin_all_rights ON global_announcement TO rsd_admin USING (TRUE) WITH CHECK (TRUE); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]> | ||
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-License-Identifier: EUPL-1.2 | ||
|
||
import Button from '@mui/material/Button' | ||
import CancelIcon from '@mui/icons-material/Cancel' | ||
import ErrorIcon from '@mui/icons-material/Error' | ||
import {useState} from 'react' | ||
|
||
export default function Announcement({announcement}: {announcement: string | null}) { | ||
const [open, setOpen] = useState(true) | ||
if (announcement === null || announcement === undefined) return null | ||
|
||
if (!open) return null | ||
|
||
return ( | ||
<div | ||
className="fixed bottom-0 right-0 w-full bg-warning text-white font-extrabold flex" | ||
> | ||
<div className='my-auto pl-5'> | ||
<ErrorIcon className='align-middle' /> | ||
</div> | ||
<div className="w-full p-5 inline-block"> | ||
<span className='align-middle'>{announcement}</span> | ||
</div> | ||
<Button | ||
className='ml-auto' | ||
onClick={() => {setOpen(false)}} | ||
> | ||
<CancelIcon className='text-white' /> | ||
</Button> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]> | ||
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) | ||
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) | ||
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// SPDX-FileCopyrightText: 2023 Netherlands eScience Center | ||
// SPDX-FileCopyrightText: 2023 dv4all | ||
// | ||
|
@@ -21,6 +23,12 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle' | |
import FluorescentIcon from '@mui/icons-material/Fluorescent' | ||
|
||
export const adminPages = { | ||
notifications: { | ||
title: 'Announcements', | ||
subtitle: 'Show global notifications to all users', | ||
icon: <DescriptionIcon />, | ||
path: '/admin/announcements', | ||
}, | ||
pages:{ | ||
title: 'Public pages', | ||
subtitle: 'Manage markdown pages', | ||
|
127 changes: 127 additions & 0 deletions
127
frontend/components/admin/announcements/AnnouncementsForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]> | ||
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) | ||
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// SPDX-FileCopyrightText: 2023 dv4all | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-License-Identifier: EUPL-1.2 | ||
|
||
import Button from '@mui/material/Button' | ||
import AddIcon from '@mui/icons-material/Add' | ||
|
||
import {useSession} from '~/auth' | ||
import {Controller, SubmitHandler, UseControllerProps, useController, useForm} from 'react-hook-form' | ||
import AutosaveControlledTextField, {OnSaveProps} from '~/components/form/AutosaveControlledTextField' | ||
import {Input, Switch} from '@mui/material' | ||
import SubmitButtonWithListener from '~/components/form/SubmitButtonWithListener' | ||
import {createJsonHeaders, extractReturnMessage, getBaseUrl} from '~/utils/fetchHelpers' | ||
import {GetServerSidePropsContext} from 'next' | ||
import logger from '~/utils/logger' | ||
import {useEffect, useState} from 'react' | ||
import ControlledSwitch from '~/components/form/ControlledSwitch' | ||
import ControlledTextField from '~/components/form/ControlledTextField' | ||
|
||
const formId = 'announcements-form' | ||
|
||
type EditAnnouncementItem = { | ||
[id: string]: any, | ||
text: string | null, | ||
enabled: boolean | ||
} | ||
|
||
type EditAnnouncementFormProps = { | ||
data: EditAnnouncementItem | ||
} | ||
|
||
export default function AnnouncementsForm({data}: EditAnnouncementFormProps) { | ||
const {token} = useSession() | ||
const {handleSubmit, register, control, setValue, formState: {errors}} = useForm<EditAnnouncementItem>({ | ||
defaultValues: { | ||
...data | ||
}, | ||
mode: 'onChange' | ||
}) | ||
|
||
function onSubmit(dataToSubmit: EditAnnouncementItem) { | ||
saveAnnouncement(dataToSubmit).then( | ||
(resp) => { | ||
if (resp && [200, 201].includes(resp.status) && resp?.object) { | ||
const newObj = resp.object[0] | ||
setValue('enabled', newObj.enabled) | ||
setValue('id', newObj.id) | ||
setValue('text', newObj.text) | ||
} else { | ||
console.log('Error saving data:', resp) | ||
} | ||
} | ||
) | ||
} | ||
|
||
async function saveAnnouncement(item: EditAnnouncementItem) { | ||
try { | ||
let method | ||
let url | ||
if (item.id == '') { | ||
delete item.id | ||
method = 'POST' | ||
url = '/api/v1/global_announcement' | ||
} else { | ||
method = 'PATCH' | ||
url = `/api/v1/global_announcement?id=eq.${item.id}` | ||
} | ||
const resp = await fetch(url, { | ||
method: method, | ||
headers: { | ||
...createJsonHeaders(token), | ||
Prefer: 'return=representation', | ||
}, | ||
body: JSON.stringify(item) | ||
}) | ||
if ([200, 201].includes(resp.status)) { | ||
return { | ||
status: 201, | ||
object: await resp.json() | ||
} | ||
} | ||
// return extractReturnMessage(resp, item.text ?? '') | ||
} catch (e: any) { | ||
logger(`saveAnnouncement: ${e?.message}`, 'error') | ||
return { | ||
status: 500, | ||
message: e?.message | ||
} | ||
} | ||
} | ||
|
||
return ( | ||
<form | ||
id={formId} | ||
onSubmit={handleSubmit(onSubmit)} | ||
className="w-full md:w-[42rem]" | ||
> | ||
<Input type="hidden" {...register('id')}/> | ||
<ControlledTextField | ||
control={control} | ||
options={{ | ||
name: 'text', | ||
label: 'Announcement' | ||
}} | ||
rules={{ | ||
maxLength: { | ||
value: 300, | ||
message: 'Maximum length is 300.' | ||
} | ||
}} | ||
/> | ||
<ControlledSwitch | ||
label='Visible' | ||
name='enabled' | ||
control={control} | ||
/> | ||
<SubmitButtonWithListener | ||
disabled={false} | ||
formId={formId} | ||
/> | ||
</form> | ||
) | ||
} |
20 changes: 20 additions & 0 deletions
20
frontend/components/admin/announcements/getAnnouncement.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]> | ||
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-License-Identifier: EUPL-1.2 | ||
|
||
import {getBaseUrl} from '~/utils/fetchHelpers' | ||
import logger from '~/utils/logger' | ||
|
||
export default async function getAnnouncement() { | ||
const url = getBaseUrl() + '/global_announcement' | ||
const resp = await fetch(url, {method: 'GET',}) | ||
const json = await resp.json() | ||
if (json[0] && json[0].enabled) { | ||
return json[0].text | ||
} else { | ||
logger('getAnnouncement failed:') | ||
return null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]> | ||
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) | ||
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// SPDX-FileCopyrightText: 2023 dv4all | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-License-Identifier: EUPL-1.2 | ||
|
||
import Button from '@mui/material/Button' | ||
import AddIcon from '@mui/icons-material/Add' | ||
|
||
import {useSession} from '~/auth' | ||
import {SubmitHandler, UseControllerProps, useController, useForm} from 'react-hook-form' | ||
import AutosaveControlledTextField, {OnSaveProps} from '~/components/form/AutosaveControlledTextField' | ||
import {Input, Switch} from '@mui/material' | ||
import SubmitButtonWithListener from '~/components/form/SubmitButtonWithListener' | ||
import {createJsonHeaders, extractReturnMessage, getBaseUrl} from '~/utils/fetchHelpers' | ||
import {GetServerSidePropsContext} from 'next' | ||
import logger from '~/utils/logger' | ||
import {useEffect, useState} from 'react' | ||
import AnnouncementsForm from './AnnouncementsForm' | ||
import ContentLoader from '~/components/layout/ContentLoader' | ||
|
||
const formId = 'announcements-form' | ||
|
||
type EditAnnouncementItem = { | ||
[id: string]: any, | ||
text: string | null, | ||
enabled: boolean | ||
} | ||
|
||
|
||
function getData({token}: {token: string}) { | ||
const [loading, setLoading] = useState(true) | ||
const [item, setItem] = useState<EditAnnouncementItem>() | ||
|
||
async function getAnnouncement() { | ||
const url = getBaseUrl() + '/global_announcement' | ||
setLoading(true) | ||
const resp = await fetch(url, { | ||
method: 'GET', | ||
headers: { | ||
...createJsonHeaders(token), | ||
} | ||
}) | ||
|
||
if (resp.status === 200) { | ||
const json = await resp.json() | ||
if (json[0]) { | ||
setItem({ | ||
id: json[0].id, | ||
enabled: json[0].enabled, | ||
text: json[0].text | ||
}) | ||
} else { | ||
setItem({ | ||
enabled: false, | ||
text: '' | ||
}) | ||
} | ||
setLoading(false) | ||
} | ||
} | ||
|
||
useEffect(() => { | ||
getAnnouncement() | ||
}, []) | ||
|
||
return { | ||
formProps: item, | ||
loading | ||
} | ||
|
||
} | ||
|
||
|
||
export default function AnnouncementsPage() { | ||
const {token} = useSession() | ||
const {formProps, loading} = getData({token}) | ||
|
||
if (loading) return ( | ||
<ContentLoader /> | ||
) | ||
|
||
if (formProps) { | ||
return ( | ||
<AnnouncementsForm data={formProps} /> | ||
) | ||
} | ||
|
||
return <ContentLoader /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) | ||
// SPDX-FileCopyrightText: 2022 - 2023 dv4all | ||
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]> | ||
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-License-Identifier: EUPL-1.2 | ||
|
||
import {IncomingMessage} from 'http' | ||
import {ParsedUrlQuery} from 'querystring' | ||
|
@@ -10,6 +13,7 @@ import logger from '~/utils/logger' | |
import {getPageLinks} from '~/components/admin/pages/useMarkdownPages' | ||
import {defaultRsdSettings, RsdSettingsState} from './rsdSettingsReducer' | ||
import defaultSettings from '~/config/defaultSettings.json' | ||
import getAnnouncement from '~/components/admin/announcements/getAnnouncement' | ||
|
||
/** | ||
* getThemeSettings from local json file | ||
|
@@ -42,6 +46,8 @@ export async function getSettingsServerSide(req: IncomingMessage | undefined, qu | |
if (typeof req === 'undefined') return defaultRsdSettings as RsdSettingsState | ||
// get links | ||
const pages = await getPageLinks({is_published: true}) | ||
// get announcments | ||
const announcement = await getAnnouncement() | ||
// extract embed flag | ||
const embed = typeof query?.embed !== 'undefined' | ||
// get settings (host and theme) | ||
|
@@ -54,6 +60,7 @@ export async function getSettingsServerSide(req: IncomingMessage | undefined, qu | |
theme: settings.theme, | ||
pages, | ||
embed, | ||
announcement: announcement | ||
} | ||
// console.group('getSettingsServerSide') | ||
// console.log('rsdSettings...', rsdSettings) | ||
|
Oops, something went wrong.