Cloudflare Workers 记事本
想记录一些安装命令文字啥的,找不到一个一劳永逸的开源項目,还是 Cloudflare 大厂靠谱需要登陆才能编辑,可以修改管理密码,黑夜模式.....演示站(代码也在这里面了): https://loc.lol部署说明:
1、创建 Cloudflare Workers (编辑代码将下面的代码复制~粘贴~部署)
2、创建 R2 存储桶(随意名称举例;AAA123)
3、在 Cloudflare Workers 设置~变量~R2 存储桶绑定~添加变量名称必须 JSBR2 选择R2存储桶 AAA123 部署即可。
注:修改了帖子里 树莓派 提出的问题手机浏览时,最底下的三两行代码会被状态栏遮住。
注:增加了帖子里 playbear 提出的问题 能不能输入密码后才能查看内容。
1、代码如下:(无需密码可查看内容)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const { pathname, searchParams } = new URL(request.url);
// Check if password protection is enabled
let ADMIN_PASSWORD = await JSBR2.get('admin_password');
ADMIN_PASSWORD = ADMIN_PASSWORD ? await ADMIN_PASSWORD.text() : null;
const isPasswordProtected = ADMIN_PASSWORD !== undefined && ADMIN_PASSWORD !== null;
console.log('isPasswordProtected:', isPasswordProtected);
if (request.method === 'GET' && pathname === '/') {
return new Response(await getHTML(isPasswordProtected), {
headers: { 'content-type': 'text/html;charset=UTF-8' },
});
}
if (request.method === 'GET' && pathname.startsWith('/notes/')) {
const key = pathname.replace('/notes/', '');
try {
const object = await JSBR2.get(key);
if (!object) {
return new Response('Note not found', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
const value = await object.text();
return new Response(value, {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
} catch (err) {
console.error('Error retrieving note:', err);
return new Response('Error retrieving note', {
status: 500,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
}
if (request.method === 'PUT' && pathname.startsWith('/notes/')) {
const key = pathname.replace('/notes/', '');
const password = searchParams.get('password');
if (isPasswordProtected && password !== ADMIN_PASSWORD) {
return new Response('Unauthorized', {
status: 401,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
try {
const value = await request.text();
await JSBR2.put(key, value);
const timestamp = new Date().toISOString();
await JSBR2.put(`${key}_timestamp`, timestamp);
return new Response('Note saved', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
} catch (err) {
console.error('Error saving note:', err);
return new Response('Error saving note', {
status: 500,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
}
if (request.method === 'POST' && pathname === '/set-password') {
const password = await request.text();
await JSBR2.put('admin_password', password);
return new Response('Password set', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
if (request.method === 'POST' && pathname === '/change-password') {
const { oldPassword, newPassword } = await request.json();
if (isPasswordProtected && oldPassword !== ADMIN_PASSWORD) {
return new Response('Unauthorized', {
status: 401,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
await JSBR2.put('admin_password', newPassword);
return new Response('Password changed', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
return new Response('Not found', {
status: 404,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
async function getHTML(isPasswordProtected) {
return `
<!DOCTYPE html>
<html>
<head>
<title>记事本</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
font-family: Arial, sans-serif;
}
textarea {
flex: 1;
width: 100%;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
resize: none;
}
.status {
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 14px;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.status-row {
display: flex;
justify-content: space-between;
width: 100%;
}
.status span, .status button {
margin: 5px 10px;
}
.password-setup,
.change-password {
display: none;
flex-direction: row;
align-items: center;
margin-top: 10px;
}
.password-setup input,
.change-password input {
margin-right: 10px;
margin-bottom: 5px;
}
.change-password input {
margin-right: 5px;
}
@media (max-width: 600px) {
.status {
position: static;
width: 100%;
padding: 5px;
font-size: 12px;
}
.status-row {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.status span, .status button {
margin: 2px 5px;
}
.password-setup,
.change-password {
flex-direction: column;
align-items: flex-start;
}
}
@media (min-width: 601px) {
.status {
position: fixed;
bottom: 10px;
right: 10px;
}
}
.dark-mode {
background-color: black;
color: white;
}
.dark-mode textarea {
background-color: black;
color: white;
}
.dark-mode #moon-icon path {
fill: white;
}
#moon-icon path {
fill: black;
}
</style>
</head>
<body>
<textarea id="note" placeholder="开始输入..."></textarea>
<div class="status" id="status">
<div class="status-row">
<span>当前已使用:<span id="used-space">0.00 KB</span></span>
<span>总剩余空间:<span id="remaining-space">9.99 GB</span></span>
</div>
<div class="status-row">
<span id="last-write-time-container">最/后写入时间:<span id="last-write-time">N/A</span></span>
<button id="change-password-button" onclick="showChangePassword()" style="display: none; margin-right: 20px;">修改密码</button>
</div>
<div class="password-setup" id="password-setup">
<input type="password" id="admin-password" placeholder="设置管理员密码" />
<button onclick="setPassword()">确认</button>
</div>
<div class="change-password" id="change-password">
<input type="password" id="old-password" placeholder="旧密码" />
<input type="password" id="new-password" placeholder="新密码" />
<button onclick="changePassword()">确定</button>
</div>
</div>
<svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" style="position: fixed; top: 10px; right: 10px; cursor: pointer;">
<path d="M12 2a10 10 0 0 0 0 20 10 10 0 0 0 0-20zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16z"/>
</svg>
<script>
const TOTAL_STORAGE = 10 * 1024 * 1024 * 1024; // 10 GB in bytes
function updateStorageStatus() {
const note = document.getElementById('note').value;
const usedBytes = new TextEncoder().encode(note).length;
const remainingBytes = TOTAL_STORAGE - usedBytes;
document.getElementById('used-space').textContent = formatBytes(usedBytes);
document.getElementById('remaining-space').textContent = formatBytes(remainingBytes);
}
function formatBytes(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let unitIndex = 0;
let value = bytes;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex++;
}
return value.toFixed(2) + ' ' + units;
}
async function saveNote() {
const note = document.getElementById('note').value;
let password = localStorage.getItem('adminPassword');
if (${isPasswordProtected} && !password) {
password = prompt('请输入管理员密码:');
if (password) {
localStorage.setItem('adminPassword', password);
document.getElementById('change-password-button').style.display = 'inline-block';
} else {
alert('需要密码才能编辑笔记。');
return;
}
}
const response = await fetch(\`/notes/my-note?password=\${password || ''}\`, {
method: 'PUT',
body: note,
headers: {
'Content-Type': 'text/plain;charset=UTF-8'
}
});
if (response.status === 401) {
alert('密码错误。请刷新页面并输入正确的密码。');
localStorage.removeItem('adminPassword');
document.getElementById('change-password-button').style.display = 'none';
return;
}
const timestamp = new Date().toISOString();
document.getElementById('last-write-time').textContent = new Date(timestamp).toLocaleString();
document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
}
async function loadNote() {
const response = await fetch('/notes/my-note');
const note = await response.text();
document.getElementById('note').value = note;
updateStorageStatus();
updateLastWriteTime(); // Initial load of last write time
}
async function updateLastWriteTime() {
const timestampResponse = await fetch('/notes/my-note_timestamp');
if (timestampResponse.ok) {
const timestamp = await timestampResponse.text();
const localTimestamp = new Date(timestamp).toLocaleString();
document.getElementById('last-write-time').textContent = localTimestamp;
document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
} else {
document.getElementById('last-write-time-container').style.display = 'none'; // 隐藏最/后写入时间
}
}
function debounce(func, wait) {
let timeout;
return function() {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const debouncedSaveNote = debounce(saveNote, 200);
document.getElementById('note').addEventListener('input', () => {
debouncedSaveNote();
updateStorageStatus();
});
window.addEventListener('load', () => {
loadNote();
setInterval(updateLastWriteTime, 1000); // 每秒更新一次最/后写入时间
const password = localStorage.getItem('adminPassword');
if (password) {
document.getElementById('change-password-button').style.display = 'inline-block';
}
});
async function setPassword() {
const password = document.getElementById('admin-password').value;
if (password) {
const response = await fetch('/set-password', {
method: 'POST',
body: password,
headers: {
'Content-Type': 'text/plain;charset=UTF-8'
}
});
if (response.ok) {
alert('管理员密码设置成功');
document.getElementById('password-setup').style.display = 'none';
} else {
alert('设置管理员密码失败');
}
} else {
alert('请输入密码');
}
}
function showChangePassword() {
const password = localStorage.getItem('adminPassword');
if (!password) {
alert('您尚未登陆,请登陆后再修改密码');
return;
}
document.getElementById('change-password').style.display = 'flex';
document.getElementById('change-password-button').style.display = 'none';
}
async function changePassword() {
const oldPassword = document.getElementById('old-password').value;
const newPassword = document.getElementById('new-password').value;
if (!oldPassword || !newPassword) {
alert('请输入旧密码和新密码');
return;
}
const response = await fetch('/change-password', {
method: 'POST',
body: JSON.stringify({ oldPassword, newPassword }),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
if (response.status === 401) {
alert('旧密码不正确');
return;
}
if (response.ok) {
alert('密码修改成功');
document.getElementById('change-password').style.display = 'none';
localStorage.setItem('adminPassword', newPassword);
} else {
alert('密码修改失败');
}
}
document.getElementById('moon-icon').addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
});
</script>
</body>
</html>
`;
}
2、代码如下:(需要密码可查看内容)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const { pathname, searchParams } = new URL(request.url);
// Check if password protection is enabled
let ADMIN_PASSWORD = await JSBR2.get('admin_password');
ADMIN_PASSWORD = ADMIN_PASSWORD ? await ADMIN_PASSWORD.text() : null;
const isPasswordProtected = ADMIN_PASSWORD !== undefined && ADMIN_PASSWORD !== null;
console.log('isPasswordProtected:', isPasswordProtected);
if (request.method === 'GET' && pathname === '/') {
return new Response(await getHTML(isPasswordProtected), {
headers: { 'content-type': 'text/html;charset=UTF-8' },
});
}
if (request.method === 'GET' && pathname.startsWith('/notes/')) {
const key = pathname.replace('/notes/', '');
const password = searchParams.get('password');
if (isPasswordProtected && password !== ADMIN_PASSWORD) {
return new Response('Unauthorized', {
status: 401,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
try {
const object = await JSBR2.get(key);
if (!object) {
return new Response('Note not found', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
const value = await object.text();
return new Response(value, {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
} catch (err) {
console.error('Error retrieving note:', err);
return new Response('Error retrieving note', {
status: 500,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
}
if (request.method === 'PUT' && pathname.startsWith('/notes/')) {
const key = pathname.replace('/notes/', '');
const password = searchParams.get('password');
if (isPasswordProtected && password !== ADMIN_PASSWORD) {
return new Response('Unauthorized', {
status: 401,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
try {
const value = await request.text();
await JSBR2.put(key, value);
const timestamp = new Date().toISOString();
await JSBR2.put(`${key}_timestamp`, timestamp);
return new Response('Note saved', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
} catch (err) {
console.error('Error saving note:', err);
return new Response('Error saving note', {
status: 500,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
}
if (request.method === 'POST' && pathname === '/set-password') {
const password = await request.text();
await JSBR2.put('admin_password', password);
return new Response('Password set', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
if (request.method === 'POST' && pathname === '/change-password') {
const { oldPassword, newPassword } = await request.json();
if (isPasswordProtected && oldPassword !== ADMIN_PASSWORD) {
return new Response('Unauthorized', {
status: 401,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
await JSBR2.put('admin_password', newPassword);
return new Response('Password changed', {
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
return new Response('Not found', {
status: 404,
headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}
async function getHTML(isPasswordProtected) {
return `
<!DOCTYPE html>
<html>
<head>
<title>记事本</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
font-family: Arial, sans-serif;
}
textarea {
flex: 1;
width: 100%;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
resize: none;
}
.status {
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 14px;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.status-row {
display: flex;
justify-content: space-between;
width: 100%;
}
.status span, .status button {
margin: 5px 10px;
}
.password-setup,
.change-password {
display: none;
flex-direction: row;
align-items: center;
margin-top: 10px;
}
.password-setup input,
.change-password input {
margin-right: 10px;
margin-bottom: 5px;
}
.change-password input {
margin-right: 5px;
}
@media (max-width: 600px) {
.status {
position: static;
width: 100%;
padding: 5px;
font-size: 12px;
}
.status-row {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.status span, .status button {
margin: 2px 5px;
}
.password-setup,
.change-password {
flex-direction: column;
align-items: flex-start;
}
}
@media (min-width: 601px) {
.status {
position: fixed;
bottom: 10px;
right: 10px;
}
}
.dark-mode {
background-color: black;
color: white;
}
.dark-mode textarea {
background-color: black;
color: white;
}
.dark-mode #moon-icon path {
fill: white;
}
#moon-icon path {
fill: black;
}
</style>
</head>
<body>
<textarea id="note" placeholder="请输入密码以查看内容" disabled></textarea>
<div class="status" id="status">
<div class="status-row">
<span>当前已使用:<span id="used-space">0.00 KB</span></span>
<span>总剩余空间:<span id="remaining-space">9.99 GB</span></span>
</div>
<div class="status-row">
<span id="last-write-time-container">最/后写入时间:<span id="last-write-time">N/A</span></span>
<button id="change-password-button" onclick="showChangePassword()" style="display: none; margin-right: 20px;">修改密码</button>
</div>
<div class="password-setup" id="password-setup">
<input type="password" id="admin-password" placeholder="设置管理员密码" />
<button onclick="setPassword()">确认</button>
</div>
<div class="change-password" id="change-password">
<input type="password" id="old-password" placeholder="旧密码" />
<input type="password" id="new-password" placeholder="新密码" />
<button onclick="changePassword()">确定</button>
</div>
</div>
<svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" style="position: fixed; top: 10px; right: 10px; cursor: pointer;">
<path d="M12 2a10 10 0 0 0 0 20 10 10 0 0 0 0-20zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16z"/>
</svg>
<script>
const TOTAL_STORAGE = 10 * 1024 * 1024 * 1024; // 10 GB in bytes
function updateStorageStatus() {
const note = document.getElementById('note').value;
const usedBytes = new TextEncoder().encode(note).length;
const remainingBytes = TOTAL_STORAGE - usedBytes;
document.getElementById('used-space').textContent = formatBytes(usedBytes);
document.getElementById('remaining-space').textContent = formatBytes(remainingBytes);
}
function formatBytes(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let unitIndex = 0;
let value = bytes;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex++;
}
return value.toFixed(2) + ' ' + units;
}
async function saveNote() {
const note = document.getElementById('note').value;
let password = localStorage.getItem('adminPassword');
if (${isPasswordProtected} && !password) {
password = prompt('请输入管理员密码:');
if (password) {
localStorage.setItem('adminPassword', password);
document.getElementById('change-password-button').style.display = 'inline-block';
} else {
alert('需要密码才能编辑笔记。');
return;
}
}
const response = await fetch(\`/notes/my-note?password=\${password || ''}\`, {
method: 'PUT',
body: note,
headers: {
'Content-Type': 'text/plain;charset=UTF-8'
}
});
if (response.status === 401) {
alert('密码错误。请刷新页面并输入正确的密码。');
localStorage.removeItem('adminPassword');
document.getElementById('change-password-button').style.display = 'none';
return;
}
const timestamp = new Date().toISOString();
document.getElementById('last-write-time').textContent = new Date(timestamp).toLocaleString();
document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
}
async function loadNote() {
let password = localStorage.getItem('adminPassword');
if (${isPasswordProtected} && !password) {
password = prompt('请输入管理员密码:');
if (password) {
localStorage.setItem('adminPassword', password);
} else {
alert('需要密码才能查看笔记内容。');
return;
}
}
const response = await fetch(\`/notes/my-note?password=\${password || ''}\`);
if (response.status === 401) {
alert('密码错误。请刷新页面并输入正确的密码。');
localStorage.removeItem('adminPassword');
return;
}
const note = await response.text();
document.getElementById('note').value = note;
document.getElementById('note').disabled = false; // 启用输入框
updateStorageStatus();
updateLastWriteTime(); // Initial load of last write time
}
async function updateLastWriteTime() {
const timestampResponse = await fetch('/notes/my-note_timestamp');
if (timestampResponse.ok) {
const timestamp = await timestampResponse.text();
const localTimestamp = new Date(timestamp).toLocaleString();
document.getElementById('last-write-time').textContent = localTimestamp;
document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
} else {
document.getElementById('last-write-time-container').style.display = 'none'; // 隐藏最/后写入时间
}
}
function debounce(func, wait) {
let timeout;
return function() {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const debouncedSaveNote = debounce(saveNote, 200);
document.getElementById('note').addEventListener('input', () => {
debouncedSaveNote();
updateStorageStatus();
});
window.addEventListener('load', () => {
loadNote();
setInterval(updateLastWriteTime, 1000); // 每秒更新一次最/后写入时间
const password = localStorage.getItem('adminPassword');
if (password) {
document.getElementById('change-password-button').style.display = 'inline-block';
}
});
async function setPassword() {
const password = document.getElementById('admin-password').value;
if (password) {
const response = await fetch('/set-password', {
method: 'POST',
body: password,
headers: {
'Content-Type': 'text/plain;charset=UTF-8'
}
});
if (response.ok) {
alert('管理员密码设置成功');
document.getElementById('password-setup').style.display = 'none';
} else {
alert('设置管理员密码失败');
}
} else {
alert('请输入密码');
}
}
function showChangePassword() {
const password = localStorage.getItem('adminPassword');
if (!password) {
alert('您尚未登陆,请登陆后再修改密码');
return;
}
document.getElementById('change-password').style.display = 'flex';
document.getElementById('change-password-button').style.display = 'none';
}
async function changePassword() {
const oldPassword = document.getElementById('old-password').value;
const newPassword = document.getElementById('new-password').value;
if (!oldPassword || !newPassword) {
alert('请输入旧密码和新密码');
return;
}
const response = await fetch('/change-password', {
method: 'POST',
body: JSON.stringify({ oldPassword, newPassword }),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
if (response.status === 401) {
alert('旧密码不正确');
return;
}
if (response.ok) {
alert('密码修改成功');
document.getElementById('change-password').style.display = 'none';
localStorage.setItem('adminPassword', newPassword);
} else {
alert('密码修改失败');
}
}
document.getElementById('moon-icon').addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
});
</script>
</body>
</html>
`;
}
页:
[1]