mirror of
				https://github.com/tormachris/cf-workers-status-page.git
				synced 2025-11-04 12:56:24 +01:00 
			
		
		
		
	Merge pull request #13 from eidam/e/kv-gc
feat: move gc monitors from cron schedule to deploy postCommands
This commit is contained in:
		
							
								
								
									
										6
									
								
								.github/workflows/deploy.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/deploy.yml
									
									
									
									
										vendored
									
									
								
							@@ -24,10 +24,12 @@ jobs:
 | 
				
			|||||||
        preCommands: |
 | 
					        preCommands: |
 | 
				
			||||||
          wrangler kv:namespace create KV_STATUS_PAGE
 | 
					          wrangler kv:namespace create KV_STATUS_PAGE
 | 
				
			||||||
          apt-get update && apt-get install -y jq
 | 
					          apt-get update && apt-get install -y jq
 | 
				
			||||||
          kv_namespace_id=$(wrangler kv:namespace list | jq -c 'map(select(.title | contains("KV_STATUS_PAGE")))' | jq ".[0].id")
 | 
					          export KV_NAMESPACE_ID=$(wrangler kv:namespace list | jq -c 'map(select(.title | contains("KV_STATUS_PAGE")))' | jq -r ".[0].id")
 | 
				
			||||||
          echo "[env.production]" >> wrangler.toml
 | 
					          echo "[env.production]" >> wrangler.toml
 | 
				
			||||||
          echo "kv_namespaces = [{binding=\"KV_STATUS_PAGE\", id=${kv_namespace_id}}]" >> wrangler.toml
 | 
					          echo "kv_namespaces = [{binding=\"KV_STATUS_PAGE\", id=\"${KV_NAMESPACE_ID}\"}]" >> wrangler.toml
 | 
				
			||||||
          [ -z "$SECRET_SLACK_WEBHOOK_URL" ] && echo "Secret SECRET_SLACK_WEBHOOK_URL not set, creating dummy one..." && SECRET_SLACK_WEBHOOK_URL="default-gh-action-secret" || true
 | 
					          [ -z "$SECRET_SLACK_WEBHOOK_URL" ] && echo "Secret SECRET_SLACK_WEBHOOK_URL not set, creating dummy one..." && SECRET_SLACK_WEBHOOK_URL="default-gh-action-secret" || true
 | 
				
			||||||
 | 
					        postCommands: |
 | 
				
			||||||
 | 
					          yarn kv-gc
 | 
				
			||||||
        secrets: |
 | 
					        secrets: |
 | 
				
			||||||
          SECRET_SLACK_WEBHOOK_URL
 | 
					          SECRET_SLACK_WEBHOOK_URL
 | 
				
			||||||
        environment: production
 | 
					        environment: production
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							@@ -12,7 +12,7 @@ You'll need a [Cloudflare Workers account](https://dash.cloudflare.com/sign-up/w
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* A workers domain set up
 | 
					* A workers domain set up
 | 
				
			||||||
* The Workers Bundled subscription \($5/mo\)
 | 
					* The Workers Bundled subscription \($5/mo\)
 | 
				
			||||||
    * [Try it now with the free tier!](https://blog.cloudflare.com/workers-kv-free-tier/) Stay tuned while we make some changes so it will completely fit in the Free Tier with a 5min check interval.
 | 
					    * [Try it now with the free tier!](https://blog.cloudflare.com/workers-kv-free-tier/) Check [more info](#workers-kv-free-tier) on how to run on Workers Free. 
 | 
				
			||||||
* Some websites/APIs to watch 🙂
 | 
					* Some websites/APIs to watch 🙂
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Also, prepare the following secrets
 | 
					Also, prepare the following secrets
 | 
				
			||||||
@@ -40,7 +40,7 @@ You can either deploy with **Cloudflare Deploy Button** using GitHub Actions or
 | 
				
			|||||||
   - Value: your-slack-webhook-url
 | 
					   - Value: your-slack-webhook-url
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
3. Navigate to the **Actions** settings in your repository and enable them
 | 
					3. Navigate to the **Actions** settings in your repository and enable them
 | 
				
			||||||
4. Edit [config.yaml](https://github.com/eidam/cf-workers-status-page/blob/main/config.yaml) to adjust configuration and list all of your websites/APIs you want to monitor
 | 
					4. Edit [config.yaml](./config.yaml) to adjust configuration and list all of your websites/APIs you want to monitor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```yaml
 | 
					   ```yaml
 | 
				
			||||||
   settings:
 | 
					   settings:
 | 
				
			||||||
@@ -74,16 +74,20 @@ You can either deploy with **Cloudflare Deploy Button** using GitHub Actions or
 | 
				
			|||||||
6. 🎉
 | 
					6. 🎉
 | 
				
			||||||
7. _\(optional\)_ Go to [Cloudflare Workers settings](https://dash.cloudflare.com/?to=/workers) and assign custom domain/route
 | 
					7. _\(optional\)_ Go to [Cloudflare Workers settings](https://dash.cloudflare.com/?to=/workers) and assign custom domain/route
 | 
				
			||||||
   * e.g. `status-page.eidam.dev/*` _\(make sure you include `/*` as the Worker also serve static files\)_
 | 
					   * e.g. `status-page.eidam.dev/*` _\(make sure you include `/*` as the Worker also serve static files\)_
 | 
				
			||||||
8. _\(optional\)_ Edit [wrangler.toml](https://github.com/eidam/cf-workers-github-releases/blob/main/wrangler.toml) to adjust Worker settings or CRON Trigger schedule
 | 
					8. _\(optional\)_ Edit [wrangler.toml](./wrangler.toml) to adjust Worker settings or CRON Trigger schedule, especially if you are on [Workers Free plan](#workers-kv-free-tier)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Deploy on your own
 | 
					### Deploy on your own
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can clone the repository yourself and use Wrangler CLI to develop/deploy, extra list of things you need to take care of:
 | 
					You can clone the repository yourself and use Wrangler CLI to develop/deploy, extra list of things you need to take care of:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* create KV namespace and add the `KV_STATUS_PAGE` binding to [wrangler.toml](https://github.com/eidam/cf-workers-github-releases/blob/main/wrangler.toml)
 | 
					* create KV namespace and add the `KV_STATUS_PAGE` binding to [wrangler.toml](./wrangler.toml)
 | 
				
			||||||
* create Worker secrets _\(optional\)_
 | 
					* create Worker secrets _\(optional\)_
 | 
				
			||||||
  * `SECRET_SLACK_WEBHOOK_URL`
 | 
					  * `SECRET_SLACK_WEBHOOK_URL`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Workers KV free tier
 | 
				
			||||||
 | 
					The Workers Free plan includes limited KV usage, in order to not deplete the quota and still have enough room for monitor status changes we recommend the following changes:
 | 
				
			||||||
 | 
					* Change the CRON trigger to 5 minutes interval (`crons = ["*/5 * * * *"]`) in [wrangler.toml](./wrangler.toml)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Known issues
 | 
					## Known issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **Max 25 monitors to watch in case you are using Slack notifications**, due to the limit of subrequests Cloudflare Worker can make \(50\).
 | 
					* **Max 25 monitors to watch in case you are using Slack notifications**, due to the limit of subrequests Cloudflare Worker can make \(50\).
 | 
				
			||||||
@@ -93,3 +97,10 @@ You can clone the repository yourself and use Wrangler CLI to develop/deploy, ex
 | 
				
			|||||||
* **KV replication lag** - You might get Slack notification instantly, however it may take couple of more seconds to see the change on your status page as [Cron Triggers are usually running on underutilized quiet hours machines](https://blog.cloudflare.com/introducing-cron-triggers-for-cloudflare-workers/#how-are-you-able-to-offer-this-feature-at-no-additional-cost).
 | 
					* **KV replication lag** - You might get Slack notification instantly, however it may take couple of more seconds to see the change on your status page as [Cron Triggers are usually running on underutilized quiet hours machines](https://blog.cloudflare.com/introducing-cron-triggers-for-cloudflare-workers/#how-are-you-able-to-offer-this-feature-at-no-additional-cost).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **Initial delay (no data)** - It takes couple of minutes to schedule and run CRON Triggers for the first time
 | 
					* **Initial delay (no data)** - It takes couple of minutes to schedule and run CRON Triggers for the first time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **Slack message for monitor just removed from the config** - It takes a couple of minutes to schedule new version of CRON Triggers, so older version of the configuration might be scheduled after deployment/gc - just ignore it or re-run the latest Github Action after a couple of minutes again.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Future plans
 | 
				
			||||||
 | 
					Stay tuned for more features coming in, like leveraging the fact that CRON instances are scheduled around the world during the day
 | 
				
			||||||
 | 
					 so we can monitor the response times. However, we will most probably wait for the [Durable Objects](https://blog.cloudflare.com/introducing-workers-durable-objects/) to be in open beta
 | 
				
			||||||
 | 
					 as they are better fit to reliably store such info.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,16 +9,18 @@
 | 
				
			|||||||
    "dev": "flareact dev",
 | 
					    "dev": "flareact dev",
 | 
				
			||||||
    "build": "flareact build",
 | 
					    "build": "flareact build",
 | 
				
			||||||
    "deploy": "flareact publish",
 | 
					    "deploy": "flareact publish",
 | 
				
			||||||
 | 
					    "kv-gc": "node ./src/cli/gcMonitors.js",
 | 
				
			||||||
    "format": "prettier --write '**/*.{js,css,json,md}'"
 | 
					    "format": "prettier --write '**/*.{js,css,json,md}'"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "flareact": "^0.8.0",
 | 
					    "flareact": "^0.9.0",
 | 
				
			||||||
    "laco": "^1.2.1",
 | 
					    "laco": "^1.2.1",
 | 
				
			||||||
    "laco-react": "^1.1.0",
 | 
					    "laco-react": "^1.1.0",
 | 
				
			||||||
    "react": "^16.13.1",
 | 
					    "react": "^16.13.1",
 | 
				
			||||||
    "react-dom": "^16.13.1"
 | 
					    "react-dom": "^16.13.1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "node-fetch": "^2.6.1",
 | 
				
			||||||
    "prettier": "^1.18.2",
 | 
					    "prettier": "^1.18.2",
 | 
				
			||||||
    "yaml-loader": "^0.6.0"
 | 
					    "yaml-loader": "^0.6.0"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								src/cli/gcMonitors.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/cli/gcMonitors.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					const yaml = require('yaml-loader')
 | 
				
			||||||
 | 
					const fetch = require('node-fetch')
 | 
				
			||||||
 | 
					const fs = require('fs')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const accountId = process.env.CF_ACCOUNT_ID
 | 
				
			||||||
 | 
					const namespaceId = process.env.KV_NAMESPACE_ID
 | 
				
			||||||
 | 
					const apiToken = process.env.CF_API_TOKEN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const kvPrefix = 's_'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!accountId || !namespaceId || !apiToken) {
 | 
				
			||||||
 | 
					  console.error("Missing required environment variables: CF_ACCOUNT_ID, KV_NAMESPACE_ID, CF_API_TOKEN")
 | 
				
			||||||
 | 
					  process.exit(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function getKvMonitors(kvPrefix) {
 | 
				
			||||||
 | 
					  const init = {
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					      'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					      Authorization: `Bearer ${apiToken}`,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const res = await fetch(
 | 
				
			||||||
 | 
					    `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/keys?limit=100&prefix=${kvPrefix}`,
 | 
				
			||||||
 | 
					    init,
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  const json = await res.json()
 | 
				
			||||||
 | 
					  return json.result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function deleteKvBulk(keys) {
 | 
				
			||||||
 | 
					  const init = {
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					      'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					      Authorization: `Bearer ${apiToken}`,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    method: 'DELETE',
 | 
				
			||||||
 | 
					    body: JSON.stringify(keys),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return await fetch(
 | 
				
			||||||
 | 
					    `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
 | 
				
			||||||
 | 
					    init,
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function loadConfig() {
 | 
				
			||||||
 | 
					  const configFile = fs.readFileSync('./config.yaml', 'utf8')
 | 
				
			||||||
 | 
					  const config = yaml(configFile)
 | 
				
			||||||
 | 
					  return JSON.parse(config)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					getKvMonitors(kvPrefix).then(async kvMonitors => {
 | 
				
			||||||
 | 
					  const config = loadConfig()
 | 
				
			||||||
 | 
					  const monitors = config.monitors.map(key => {
 | 
				
			||||||
 | 
					    return key.id
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  const kvState = kvMonitors.map(key => {
 | 
				
			||||||
 | 
					    return key.name
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  const keysForRemoval = kvState.filter(
 | 
				
			||||||
 | 
					    x => !monitors.includes(x.replace(kvPrefix, '')),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (keysForRemoval.length > 0) {
 | 
				
			||||||
 | 
					    console.log(
 | 
				
			||||||
 | 
					      `Removing following keys from KV storage as they are no longer in the config: ${keysForRemoval.join(
 | 
				
			||||||
 | 
					        ', ',
 | 
				
			||||||
 | 
					      )}`,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    await deleteKvBulk(keysForRemoval)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}).catch(e => console.log(e))
 | 
				
			||||||
@@ -3,7 +3,6 @@ import config from '../../config.yaml'
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
  setKV,
 | 
					  setKV,
 | 
				
			||||||
  getKVWithMetadata,
 | 
					  getKVWithMetadata,
 | 
				
			||||||
  gcMonitors,
 | 
					 | 
				
			||||||
  getKV,
 | 
					  getKV,
 | 
				
			||||||
  notifySlack,
 | 
					  notifySlack,
 | 
				
			||||||
} from './helpers'
 | 
					} from './helpers'
 | 
				
			||||||
@@ -70,8 +69,5 @@ export async function processCronTrigger(event) {
 | 
				
			|||||||
  const loc = res.headers.get('cf-ray').split('-')[1]
 | 
					  const loc = res.headers.get('cf-ray').split('-')[1]
 | 
				
			||||||
  await setKV('lastUpdate', Date.now(), { loc })
 | 
					  await setKV('lastUpdate', Date.now(), { loc })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // gc monitor statuses
 | 
					 | 
				
			||||||
  event.waitUntil(gcMonitors(config))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return new Response('OK')
 | 
					  return new Response('OK')
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,26 +56,6 @@ export async function deleteKV(key) {
 | 
				
			|||||||
  return KV_STATUS_PAGE.delete(key)
 | 
					  return KV_STATUS_PAGE.delete(key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function gcMonitors(config) {
 | 
					 | 
				
			||||||
  const checkKvPrefix = 's_'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const monitors = config.monitors.map(key => {
 | 
					 | 
				
			||||||
    return key.id
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const kvMonitors = await listKV(checkKvPrefix)
 | 
					 | 
				
			||||||
  const kvState = kvMonitors.keys.map(key => {
 | 
					 | 
				
			||||||
    return key.metadata.id
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const keysForRemoval = kvState.filter(x => !monitors.includes(x))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (const key of keysForRemoval) {
 | 
					 | 
				
			||||||
    console.log('gc: deleting ' + checkKvPrefix + key)
 | 
					 | 
				
			||||||
    await deleteKV(checkKvPrefix + key)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function notifySlack(monitor, newMetadata) {
 | 
					export async function notifySlack(monitor, newMetadata) {
 | 
				
			||||||
  const payload = {
 | 
					  const payload = {
 | 
				
			||||||
    attachments: [
 | 
					    attachments: [
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								yarn.lock
									
									
									
									
									
								
							@@ -2947,10 +2947,10 @@ findup-sync@^3.0.0:
 | 
				
			|||||||
    micromatch "^3.0.4"
 | 
					    micromatch "^3.0.4"
 | 
				
			||||||
    resolve-dir "^1.0.1"
 | 
					    resolve-dir "^1.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
flareact@^0.8.0:
 | 
					flareact@^0.9.0:
 | 
				
			||||||
  version "0.8.0"
 | 
					  version "0.9.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/flareact/-/flareact-0.8.0.tgz#c7653f7278abee04353c5bf087af4105b7895c0a"
 | 
					  resolved "https://registry.yarnpkg.com/flareact/-/flareact-0.9.0.tgz#c16ded48f217010452a509e02b754f84eb26878c"
 | 
				
			||||||
  integrity sha512-ewjFqrxSXPBppyZtVBTD3W4iIcMB2wORpYX1QofOa9QRuy7dJ2nK4oMvCNmPJPelqdN5MF+fUBTIfMGqO3A1OA==
 | 
					  integrity sha512-YT1nGqusHTJDreU5gQezKQNU2Pszez+M3v5IrKIEtOD3ABQal+cVoWzRQGQTWMKryrUpWB0Z0nRhLYDutD8xdQ==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/core" "^7.11.0"
 | 
					    "@babel/core" "^7.11.0"
 | 
				
			||||||
    "@babel/plugin-transform-runtime" "^7.11.0"
 | 
					    "@babel/plugin-transform-runtime" "^7.11.0"
 | 
				
			||||||
@@ -4354,6 +4354,11 @@ nice-try@^1.0.4:
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
 | 
					  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
 | 
				
			||||||
  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 | 
					  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					node-fetch@^2.6.1:
 | 
				
			||||||
 | 
					  version "2.6.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
 | 
				
			||||||
 | 
					  integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-forge@^0.10.0:
 | 
					node-forge@^0.10.0:
 | 
				
			||||||
  version "0.10.0"
 | 
					  version "0.10.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
 | 
					  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user