gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 17eecfb6d9f50821d7822d60fa4135d59ea5ca57
parent 993aae5e48a5a3b47a7c7bb3cb66e2d8abda17b2
Author: f0x52 <f0x@cthu.lu>
Date:   Wed, 25 Jan 2023 18:06:41 +0100

[feature] Public list of suspended domains (#1362)

* basic rendered domain blocklist (unauthenticated!)

* style basic domain block list

* better formatting for domain blocklist

* add opt-in config option for showing suspended domains

* format/linter

* re-use InstancePeersGet for web-accessible domain blocklist

* reword explanation, border styling

* always attach blocklist handler, update error message

* domain blocklist error message grammar
Diffstat:
Mexample/config.yaml | 10++++++++--
Minternal/api/client/instance/instancepeersget.go | 17++++++++++++++++-
Minternal/config/config.go | 1+
Minternal/config/defaults.go | 1+
Minternal/config/flags.go | 1+
Minternal/config/helpers.gen.go | 25+++++++++++++++++++++++++
Minternal/processing/instance.go | 13+------------
Minternal/processing/processor.go | 2+-
Ainternal/web/domain-blocklist.go | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/web/robots.go | 4+++-
Minternal/web/web.go | 2++
Mtest/envparsing.sh | 3++-
Mtestrig/config.go | 1+
Mweb/source/css/_colors.css | 8++++----
Mweb/source/css/base.css | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mweb/source/settings/style.css | 47++---------------------------------------------
Aweb/template/domain-blocklist.tmpl | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
17 files changed, 267 insertions(+), 67 deletions(-)

diff --git a/example/config.yaml b/example/config.yaml @@ -292,6 +292,12 @@ instance-expose-peers: false # Default: false instance-expose-suspended: false +# Bool. Allow unauthenticated users to view /about/suspended, +# showing the HTML rendered list of instances that this instance blocks/suspends. +# Options: [true, false] +# Default: false +instance-expose-suspended-web: false + # Bool. Allow unauthenticated users to make queries to /api/v1/timelines/public in order # to see a list of public posts on this server. Even if set to 'false', then authenticated # users (members of the instance) will still be able to query the endpoint. @@ -700,7 +706,7 @@ advanced-cookies-samesite: "lax" advanced-rate-limit-requests: 300 # Int. Amount of open requests to permit per CPU, per router grouping, before applying http -# request throttling. Any requests beyond the calculated limit are held in a backlog queue for +# request throttling. Any requests beyond the calculated limit are held in a backlog queue for # up to 30 seconds before either being processed or timing out. Requests that don't fit in the backlog # queue will have status 503 returned to them, and the header 'Retry-After' will be set to 30 seconds. # @@ -718,7 +724,7 @@ advanced-rate-limit-requests: 300 # 2 cpu = 08 open, 032 backlog # 4 cpu = 16 open, 064 backlog # -# A multiplier of 8 is a sensible default, but you may wish to increase this for instances +# A multiplier of 8 is a sensible default, but you may wish to increase this for instances # running on very performant hardware, or decrease it for instances using v. slow CPUs. # # If you set this to 0 or less, http request throttling will be disabled entirely. diff --git a/internal/api/client/instance/instancepeersget.go b/internal/api/client/instance/instancepeersget.go @@ -24,6 +24,7 @@ import ( "strings" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" + "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -105,6 +106,8 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) { return } + var isUnauthenticated = authed.Account == nil || authed.User == nil + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return @@ -136,7 +139,19 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) { flat = true } - data, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), authed, includeSuspended, includeOpen, flat) + if includeOpen && !config.GetInstanceExposePeers() && isUnauthenticated { + err := fmt.Errorf("peers open query requires an authenticated account/user") + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + return + } + + if includeSuspended && !config.GetInstanceExposeSuspended() && isUnauthenticated { + err := fmt.Errorf("peers suspended query requires an authenticated account/user") + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + return + } + + data, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), includeSuspended, includeOpen, flat) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return diff --git a/internal/config/config.go b/internal/config/config.go @@ -76,6 +76,7 @@ type Configuration struct { InstanceExposePeers bool `name:"instance-expose-peers" usage:"Allow unauthenticated users to query /api/v1/instance/peers?filter=open"` InstanceExposeSuspended bool `name:"instance-expose-suspended" usage:"Expose suspended instances via web UI, and allow unauthenticated users to query /api/v1/instance/peers?filter=suspended"` + InstanceExposeSuspendedWeb bool `name:"instance-expose-suspended-web" usage:"Expose list of suspended instances as webpage on /about/suspended"` InstanceExposePublicTimeline bool `name:"instance-expose-public-timeline" usage:"Allow unauthenticated users to query /api/v1/timelines/public"` InstanceDeliverToSharedInboxes bool `name:"instance-deliver-to-shared-inboxes" usage:"Deliver federated messages to shared inboxes, if they're available."` diff --git a/internal/config/defaults.go b/internal/config/defaults.go @@ -58,6 +58,7 @@ var Defaults = Configuration{ InstanceExposePeers: false, InstanceExposeSuspended: false, + InstanceExposeSuspendedWeb: false, InstanceDeliverToSharedInboxes: true, AccountsRegistrationOpen: true, diff --git a/internal/config/flags.go b/internal/config/flags.go @@ -78,6 +78,7 @@ func (s *ConfigState) AddServerFlags(cmd *cobra.Command) { // Instance cmd.Flags().Bool(InstanceExposePeersFlag(), cfg.InstanceExposePeers, fieldtag("InstanceExposePeers", "usage")) cmd.Flags().Bool(InstanceExposeSuspendedFlag(), cfg.InstanceExposeSuspended, fieldtag("InstanceExposeSuspended", "usage")) + cmd.Flags().Bool(InstanceExposeSuspendedWebFlag(), cfg.InstanceExposeSuspendedWeb, fieldtag("InstanceExposeSuspendedWeb", "usage")) cmd.Flags().Bool(InstanceDeliverToSharedInboxesFlag(), cfg.InstanceDeliverToSharedInboxes, fieldtag("InstanceDeliverToSharedInboxes", "usage")) // Accounts diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go @@ -724,6 +724,31 @@ func GetInstanceExposeSuspended() bool { return global.GetInstanceExposeSuspende // SetInstanceExposeSuspended safely sets the value for global configuration 'InstanceExposeSuspended' field func SetInstanceExposeSuspended(v bool) { global.SetInstanceExposeSuspended(v) } +// GetInstanceExposeSuspendedWeb safely fetches the Configuration value for state's 'InstanceExposeSuspendedWeb' field +func (st *ConfigState) GetInstanceExposeSuspendedWeb() (v bool) { + st.mutex.Lock() + v = st.config.InstanceExposeSuspendedWeb + st.mutex.Unlock() + return +} + +// SetInstanceExposeSuspendedWeb safely sets the Configuration value for state's 'InstanceExposeSuspendedWeb' field +func (st *ConfigState) SetInstanceExposeSuspendedWeb(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.InstanceExposeSuspendedWeb = v + st.reloadToViper() +} + +// InstanceExposeSuspendedWebFlag returns the flag name for the 'InstanceExposeSuspendedWeb' field +func InstanceExposeSuspendedWebFlag() string { return "instance-expose-suspended-web" } + +// GetInstanceExposeSuspendedWeb safely fetches the value for global configuration 'InstanceExposeSuspendedWeb' field +func GetInstanceExposeSuspendedWeb() bool { return global.GetInstanceExposeSuspendedWeb() } + +// SetInstanceExposeSuspendedWeb safely sets the value for global configuration 'InstanceExposeSuspendedWeb' field +func SetInstanceExposeSuspendedWeb(v bool) { global.SetInstanceExposeSuspendedWeb(v) } + // GetInstanceExposePublicTimeline safely fetches the Configuration value for state's 'InstanceExposePublicTimeline' field func (st *ConfigState) GetInstanceExposePublicTimeline() (v bool) { st.mutex.Lock() diff --git a/internal/processing/instance.go b/internal/processing/instance.go @@ -28,7 +28,6 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/text" "github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/validate" @@ -48,15 +47,10 @@ func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.I return ai, nil } -func (p *processor) InstancePeersGet(ctx context.Context, authed *oauth.Auth, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) { +func (p *processor) InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) { domains := []*apimodel.Domain{} if includeOpen { - if !config.GetInstanceExposePeers() && (authed.Account == nil || authed.User == nil) { - err := fmt.Errorf("peers open query requires an authenticated account/user") - return nil, gtserror.NewErrorUnauthorized(err, err.Error()) - } - instances, err := p.db.GetInstancePeers(ctx, false) if err != nil && err != db.ErrNoEntries { err = fmt.Errorf("error selecting instance peers: %s", err) @@ -70,11 +64,6 @@ func (p *processor) InstancePeersGet(ctx context.Context, authed *oauth.Auth, in } if includeSuspended { - if !config.GetInstanceExposeSuspended() && (authed.Account == nil || authed.User == nil) { - err := fmt.Errorf("peers suspended query requires an authenticated account/user") - return nil, gtserror.NewErrorUnauthorized(err, err.Error()) - } - domainBlocks := []*gtsmodel.DomainBlock{} if err := p.db.GetAll(ctx, &domainBlocks); err != nil && err != db.ErrNoEntries { return nil, gtserror.NewErrorInternalError(err) diff --git a/internal/processing/processor.go b/internal/processing/processor.go @@ -172,7 +172,7 @@ type Processor interface { // InstanceGet retrieves instance information for serving at api/v1/instance InstanceGet(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode) - InstancePeersGet(ctx context.Context, authed *oauth.Auth, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) + InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) // InstancePatch updates this instance according to the given form. // // It should already be ascertained that the requesting account is authenticated and an admin. diff --git a/internal/web/domain-blocklist.go b/internal/web/domain-blocklist.go @@ -0,0 +1,71 @@ +/* + GoToSocial + Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package web + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +const ( + domainBlockListPath = "/about/suspended" +) + +func (m *Module) domainBlockListGETHandler(c *gin.Context) { + authed, err := oauth.Authed(c, false, false, false, false) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + return + } + + if !config.GetInstanceExposeSuspendedWeb() && (authed.Account == nil || authed.User == nil) { + err := fmt.Errorf("this instance does not expose the list of suspended domains publicly") + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + return + } + + host := config.GetHost() + instance, err := m.processor.InstanceGet(c.Request.Context(), host) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) + return + } + + domainBlocks, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), true, false, false) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + return + } + + c.HTML(http.StatusOK, "domain-blocklist.tmpl", gin.H{ + "instance": instance, + "ogMeta": ogBase(instance), + "blocklist": domainBlocks, + "stylesheets": []string{ + assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css", + }, + "javascript": []string{distPathPrefix + "/frontend.js"}, + }) +} diff --git a/internal/web/robots.go b/internal/web/robots.go @@ -49,7 +49,9 @@ Disallow: /emoji/ # panels Disallow: /admin Disallow: /user -Disallow: /settings/` +Disallow: /settings/ +# domain blocklist +Disallow: /about/suspended` ) // robotsGETHandler returns a decent robots.txt that prevents crawling diff --git a/internal/web/web.go b/internal/web/web.go @@ -100,6 +100,8 @@ func (m *Module) Route(r router.Router, mi ...gin.HandlerFunc) { r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler) r.AttachHandler(http.MethodGet, robotsPath, m.robotsGETHandler) + r.AttachHandler(http.MethodGet, domainBlockListPath, m.domainBlockListGETHandler) + /* Attach redirects from old endpoints to current ones for backwards compatibility */ diff --git a/test/envparsing.sh b/test/envparsing.sh @@ -2,7 +2,7 @@ set -eu -EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"report-max-size":100,"report-sweep-freq":10000000000,"report-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-sqlite-busy-timeout":1000000000,"db-sqlite-cache-size":0,"db-sqlite-journal-mode":"DELETE","db-sqlite-synchronous":"FULL","db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}' +EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"report-max-size":100,"report-sweep-freq":10000000000,"report-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-sqlite-busy-timeout":1000000000,"db-sqlite-cache-size":0,"db-sqlite-journal-mode":"DELETE","db-sqlite-synchronous":"FULL","db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"instance-expose-suspended-web":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}' # Set all the environment variables to # ensure that these are parsed without panic @@ -32,6 +32,7 @@ GTS_WEB_TEMPLATE_BASE_DIR='/root' \ GTS_WEB_ASSET_BASE_DIR='/root' \ GTS_INSTANCE_EXPOSE_PEERS=true \ GTS_INSTANCE_EXPOSE_SUSPENDED=true \ +GTS_INSTANCE_EXPOSE_SUSPENDED_WEB=true \ GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE=true \ GTS_INSTANCE_DELIVER_TO_SHARED_INBOXES=false \ GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \ diff --git a/testrig/config.go b/testrig/config.go @@ -62,6 +62,7 @@ var testDefaults = config.Configuration{ InstanceExposePeers: true, InstanceExposeSuspended: true, + InstanceExposeSuspendedWeb: true, InstanceDeliverToSharedInboxes: true, AccountsRegistrationOpen: true, diff --git a/web/source/css/_colors.css b/web/source/css/_colors.css @@ -114,6 +114,6 @@ $settings-nav-bg-active: $gray2; $error-fg: $error1; $error-bg: $error2; -$settings-entry-bg: $gray2; -$settings-entry-alternate-bg: $gray3; -$settings-entry-hover-bg: $gray4; -\ No newline at end of file +$list-entry-bg: $gray2; +$list-entry-alternate-bg: $gray3; +$list-entry-hover-bg: $gray4; +\ No newline at end of file diff --git a/web/source/css/base.css b/web/source/css/base.css @@ -413,4 +413,80 @@ label { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; +} + +.list { + display: flex; + flex-direction: column; + + &.scrolling { + max-height: 40rem; + overflow: auto; + } + + .header, .entry { + padding: 0.5rem; + } + + .header { + border: 0.1rem solid transparent !important; /* for alignment with .entry border padding */ + background: $gray1 !important; + display: flex; + font-weight: bold; + } + + input[type=checkbox] { + margin-left: 0.5rem; + } + + .entry { + display: flex; + flex-wrap: wrap; + background: $list-entry-bg; + border: 0.1rem solid transparent; + + &:nth-child(even) { + background: $list-entry-alternate-bg; + } + + &:hover { + background: $list-entry-hover-bg; + } + + &:active, &:focus, &:hover, &:target { + border-color: $fg-accent; + } + } +} + +.domain-blocklist { + box-shadow: $boxshadow; + + .entry { + display: grid; + grid-template-columns: 15rem 1fr; + gap: 0.5rem; + align-items: start; + border: $boxshadow-border; + border-top-color: transparent; + + & > div { + display: flex; + align-items: center + } + + .domain a { + font-weight: bold; + text-decoration: none; + display: inline-block; /* so it wraps properly */ + } + + .public_comment p { + margin: 0; + } + } + + .header .domain { + color: $fg; + } } \ No newline at end of file diff --git a/web/source/settings/style.css b/web/source/settings/style.css @@ -368,49 +368,6 @@ span.form-info { font-weight: initial; } -.list { - display: flex; - flex-direction: column; - - &.scrolling { - max-height: 40rem; - overflow: auto; - } - - .header, .entry { - padding: 0.5rem; - } - - .header { - border: 0.1rem solid transparent; /* for alignment with .entry border padding */ - background: $gray2; - display: flex; - } - - input[type=checkbox] { - margin-left: 0.5rem; - } - - .entry { - display: flex; - flex-wrap: wrap; - background: $settings-entry-bg; - border: 0.1rem solid transparent; - - &:nth-child(even) { - background: $settings-entry-alternate-bg; - } - - &:hover { - background: $settings-entry-hover-bg; - } - - &:active, &:focus, &:hover { - border-color: $fg-accent; - } - } -} - .checkbox-list { .header, .entry { gap: 1rem; @@ -446,7 +403,7 @@ span.form-info { } .emoji-list { - background: $settings-entry-bg; + background: $list-entry-bg; .entry { flex-direction: column; @@ -472,7 +429,7 @@ span.form-info { } &:hover { - background: $settings-entry-hover-bg; + background: $list-entry-hover-bg; } } } diff --git a/web/template/domain-blocklist.tmpl b/web/template/domain-blocklist.tmpl @@ -0,0 +1,51 @@ +{{- /* + GoToSocial + Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ -}} + +{{ template "header.tmpl" .}} +<main> + <section> + <h1>Suspended Instances</h1> + <p> + The following list of domains have been suspended by the administrator(s) of this server. + </p> + <p> + All current and future accounts on these instances are blocked, and no more data is federated to the remote + servers. + This extends to subdomains, so an entry for 'example.com' includes 'social.example.com' as well. + </p> + <div class="list domain-blocklist"> + <div class="header entry"> + <div class="domain">Domain</div> + <div class="public_comment">Public comment</div> + </div> + {{range .blocklist}} + <div class="entry" id="{{.Domain}}"> + <div class="domain"> + <a class="text-cutoff" href="#{{.Domain}}" title="{{.Domain}}">{{.Domain}}</a> + </div> + <div class="public_comment"> + <p> + {{.PublicComment}} + </p> + </div> + </div> + {{end}} + </div> + </section> +</main> +{{ template "footer.tmpl" .}} +\ No newline at end of file