♻️ 使用 Actix RS 重构 #8

Merged
LittleSheep merged 7 commits from refactor/actix-rs into refactor/rust 2024-02-13 12:39:08 +00:00
77 changed files with 7315 additions and 1644 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -5,24 +5,24 @@ on:
branches: [ master ] branches: [ master ]
jobs: jobs:
build-docker: build-image:
runs-on: edge runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile
push: true push: true
tags: xsheep2010/roadsign:nightly file: ./Dockerfile
tags: xsheep2010/roadsign:sigma

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
/config /config
/certs
/letsencrypt /letsencrypt
# Added by cargo # Added by cargo

1717
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,24 +6,18 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
config = { version = "0.13.4", features = ["toml"] } actix-files = "0.6.5"
futures-util = "0.3.30" actix-proxy = "0.2.0"
http = "1.0.0" actix-web = { version = "4.5.1", features = ["rustls-0_22"] }
hyper-util = { version = "0.1.2", features = ["full"] } actix-web-httpauth = "0.8.1"
awc = "3.4.0"
config = { version = "0.14.0", features = ["toml"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
mime = "0.3.17" mime = "0.3.17"
percent-encoding = "2.3.1" percent-encoding = "2.3.1"
poem = { version = "2.0.0", features = [
"tokio-metrics",
"websocket",
"static-files",
"reqwest",
] }
poem-openapi = { version = "4.0.0" }
queryst = "3.0.0" queryst = "3.0.0"
rand = "0.8.5" rand = "0.8.5"
regex = "1.10.2" regex = "1.10.2"
reqwest = { git = "https://github.com/seanmonstar/reqwest.git", branch = "hyper-v1", version = "0.11.23" }
serde = "1.0.195" serde = "1.0.195"
serde_json = "1.0.111" serde_json = "1.0.111"
tokio = { version = "1.35.1", features = [ tokio = { version = "1.35.1", features = [
@ -37,3 +31,6 @@ toml = "0.8.8"
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = "0.3.18"
wildmatch = "2.3.0" wildmatch = "2.3.0"
derive_more = "0.99.17"
rustls = "0.22.2"
rustls-pemfile = "2.0.0"

View File

@ -9,7 +9,7 @@ WORKDIR /source/pkg/sideload/view
RUN npm install RUN npm install
RUN npm run build RUN npm run build
WORKDIR /source WORKDIR /source
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/cmd/server/main.go RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/cmd/server.rs/main.go
# Runtime # Runtime
FROM golang:alpine FROM golang:alpine

View File

@ -74,9 +74,9 @@ rds cli with this command.
```shell ```shell
rds connect <id> <url> <password> rds connect <id> <url> <password>
# ID will allow you find this server in after commands. # ID will allow you find this server.rs in after commands.
# URL is to your roadsign server sideload api. # URL is to your roadsign server.rs sideload api.
# Password is your roadsign server credential. # Password is your roadsign server.rs credential.
# ====================================================================== # ======================================================================
# !WARNING! All these things will storage in your $HOME/.roadsignrc.yaml # !WARNING! All these things will storage in your $HOME/.roadsignrc.yaml
# ====================================================================== # ======================================================================
@ -85,8 +85,8 @@ rds connect <id> <url> <password>
Then, sync your local config to remote. Then, sync your local config to remote.
```shell ```shell
rds sync <server id> <site id> <config file> rds sync <server.rs id> <site id> <config file>
# Server ID is your server added by last command. # Server ID is your server.rs added by last command.
# Site ID is your new site id or old site id if you need update it. # Site ID is your new site id or old site id if you need update it.
# Config File is your local config file path. # Config File is your local config file path.
``` ```

View File

@ -1,7 +1,17 @@
regions = "./regions" regions = "./regions"
secret = "aEXcED5xJ3" secret = "aEXcED5xJ3"
[listen] [sideload]
proxies = "0.0.0.0:80" bind_addr = "0.0.0.0:81"
proxies_tls = "0.0.0.0:443"
sideload = "0.0.0.0:81" [[proxies.bind]]
addr = "0.0.0.0:80"
tls = false
[[proxies.bind]]
addr = "0.0.0.0:443"
tls = false
[[certificates]]
domain = "localhost"
certs = "certs/fullchain.pem"
key = "certs/privkey.pem"

View File

@ -1,50 +0,0 @@
use http::StatusCode;
use poem::{
web::headers::{self, authorization::Basic, HeaderMapExt},
Endpoint, Error, Middleware, Request, Response, Result,
};
pub struct BasicAuth {
pub username: String,
pub password: String,
}
impl<E: Endpoint> Middleware<E> for BasicAuth {
type Output = BasicAuthEndpoint<E>;
fn transform(&self, ep: E) -> Self::Output {
BasicAuthEndpoint {
ep,
username: self.username.clone(),
password: self.password.clone(),
}
}
}
pub struct BasicAuthEndpoint<E> {
ep: E,
username: String,
password: String,
}
#[poem::async_trait]
impl<E: Endpoint> Endpoint for BasicAuthEndpoint<E> {
type Output = E::Output;
async fn call(&self, req: Request) -> Result<Self::Output> {
if let Some(auth) = req.headers().typed_get::<headers::Authorization<Basic>>() {
if auth.0.username() == self.username && auth.0.password() == self.password {
return self.ep.call(req).await;
}
}
Err(Error::from_response(
Response::builder()
.header(
"WWW-Authenticate",
"Basic realm=\"RoadSig\", charset=\"UTF-8\"",
)
.status(StatusCode::UNAUTHORIZED)
.finish(),
))
}
}

View File

@ -7,5 +7,5 @@ use crate::config::loader::load_settings;
pub mod loader; pub mod loader;
lazy_static! { lazy_static! {
pub static ref C: RwLock<Config> = RwLock::new(load_settings()); pub static ref CFG: RwLock<Config> = RwLock::new(load_settings());
} }

View File

@ -1,28 +1,28 @@
pub mod auth; extern crate core;
mod config; mod config;
mod proxies; mod proxies;
mod sideload; mod sideload;
pub mod warden; mod warden;
mod server;
pub mod tls;
use std::error;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use poem::{listener::TcpListener, EndpointExt, Route, Server};
use poem_openapi::OpenApiService;
use proxies::RoadInstance; use proxies::RoadInstance;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tokio::task::JoinSet;
use tracing::{error, info, Level}; use tracing::{error, info, Level};
use crate::proxies::server::build_proxies;
use crate::proxies::route; use crate::sideload::server::build_sideload;
lazy_static! { lazy_static! {
static ref ROAD: Mutex<RoadInstance> = Mutex::new(RoadInstance::new()); static ref ROAD: Mutex<RoadInstance> = Mutex::new(RoadInstance::new());
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), std::io::Error> { async fn main() -> Result<(), Box<dyn error::Error>> {
// Setting up logging // Setting up logging
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "poem=debug");
}
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_max_level(Level::DEBUG) .with_max_level(Level::DEBUG)
.init(); .init();
@ -30,11 +30,10 @@ async fn main() -> Result<(), std::io::Error> {
// Prepare all the stuff // Prepare all the stuff
info!("Loading proxy regions..."); info!("Loading proxy regions...");
match proxies::loader::scan_regions( match proxies::loader::scan_regions(
config::C config::CFG
.read() .read()
.await .await
.get_string("regions") .get_string("regions")?
.unwrap_or("./regions".to_string()),
) { ) {
Err(_) => error!("Loading proxy regions... failed"), Err(_) => error!("Loading proxy regions... failed"),
Ok((regions, count)) => { Ok((regions, count)) => {
@ -43,37 +42,15 @@ async fn main() -> Result<(), std::io::Error> {
} }
}; };
let mut server_set = JoinSet::new();
// Proxies // Proxies
let proxies_server = Server::new(TcpListener::bind( for server in build_proxies().await? {
config::C server_set.spawn(server);
.read() }
.await
.get_string("listen.proxies")
.unwrap_or("0.0.0.0:80".to_string()),
))
.run(route::handle);
// Sideload // Sideload
let sideload = OpenApiService::new(sideload::SideloadApi, "Sideload API", "1.0") server_set.spawn(build_sideload().await?);
.server("http://localhost:3000/cgi");
let sideload_server = Server::new(TcpListener::bind(
config::C
.read()
.await
.get_string("listen.sideload")
.unwrap_or("0.0.0.0:81".to_string()),
))
.run(
Route::new().nest("/cgi", sideload).with(auth::BasicAuth {
username: "RoadSign".to_string(),
password: config::C
.read()
.await
.get_string("secret")
.unwrap_or("password".to_string()),
}),
);
// Process manager // Process manager
{ {
@ -85,7 +62,8 @@ async fn main() -> Result<(), std::io::Error> {
app.warden.start().await; app.warden.start().await;
} }
tokio::try_join!(proxies_server, sideload_server)?; // Wait for web servers
server_set.join_next().await;
Ok(()) Ok(())
} }

View File

@ -1,52 +0,0 @@
use std::fmt::Write;
pub struct DirectoryTemplate<'a> {
pub path: &'a str,
pub files: Vec<FileRef>,
}
impl<'a> DirectoryTemplate<'a> {
pub fn render(&self) -> String {
let mut s = format!(
r#"
<html>
<head>
<title>Index of {}</title>
</head>
<body>
<h1>Index of /{}</h1>
<ul>"#,
self.path, self.path
);
for file in &self.files {
if file.is_dir {
let _ = write!(
s,
r#"<li><a href="{}">{}/</a></li>"#,
file.url, file.filename
);
} else {
let _ = write!(
s,
r#"<li><a href="{}">{}</a></li>"#,
file.url, file.filename
);
}
}
s.push_str(
r#"</ul>
</body>
</html>"#,
);
s
}
}
pub struct FileRef {
pub url: String,
pub filename: String,
pub is_dir: bool,
}

View File

@ -1,6 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use poem_openapi::Object;
use queryst::parse; use queryst::parse;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
@ -9,14 +8,14 @@ use crate::warden::Application;
use super::responder::StaticResponderConfig; use super::responder::StaticResponderConfig;
#[derive(Debug, Object, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Region { pub struct Region {
pub id: String, pub id: String,
pub locations: Vec<Location>, pub locations: Vec<Location>,
pub applications: Vec<Application>, pub applications: Vec<Application>,
} }
#[derive(Debug, Object, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location { pub struct Location {
pub id: String, pub id: String,
pub hosts: Vec<String>, pub hosts: Vec<String>,
@ -27,7 +26,7 @@ pub struct Location {
pub destinations: Vec<Destination>, pub destinations: Vec<Destination>,
} }
#[derive(Debug, Object, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Destination { pub struct Destination {
pub id: String, pub id: String,
pub uri: String, pub uri: String,
@ -75,15 +74,6 @@ impl Destination {
.collect::<Vec<_>>()[0] .collect::<Vec<_>>()[0]
} }
pub fn get_websocket_uri(&self) -> Result<String, ()> {
let parts = self.uri.as_str().splitn(2, "://").collect::<Vec<_>>();
let url = parts.get(1).unwrap_or(&"");
match self.get_protocol() {
"http" | "https" => Ok(url.replace("http", "ws")),
_ => Err(()),
}
}
pub fn get_hypertext_uri(&self) -> Result<String, ()> { pub fn get_hypertext_uri(&self) -> Result<String, ()> {
match self.get_protocol() { match self.get_protocol() {
"http" => Ok("http://".to_string() + self.get_host()), "http" => Ok("http://".to_string() + self.get_host()),

View File

@ -1,21 +1,30 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use poem_openapi::Object;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::config::{Destination, Location, Region}; use super::config::{Destination, Location, Region};
#[derive(Debug, Object, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RoadTrace { pub struct RoadTrace {
pub region: String, pub region: String,
pub location: String, pub location: String,
pub destination: String, pub destination: String,
pub ip_address: String,
pub user_agent: String,
pub error: Option<String>, pub error: Option<String>,
} }
impl RoadTrace { impl RoadTrace {
pub fn from_structs(reg: Region, loc: Location, end: Destination) -> RoadTrace { pub fn from_structs(
ip: String,
ua: String,
reg: Region,
loc: Location,
end: Destination,
) -> RoadTrace {
RoadTrace { RoadTrace {
ip_address: ip,
user_agent: ua,
region: reg.id, region: reg.id,
location: loc.id, location: loc.id,
destination: end.id, destination: end.id,
@ -24,17 +33,16 @@ impl RoadTrace {
} }
pub fn from_structs_with_error( pub fn from_structs_with_error(
ip: String,
ua: String,
reg: Region, reg: Region,
loc: Location, loc: Location,
end: Destination, end: Destination,
err: String, err: String,
) -> RoadTrace { ) -> RoadTrace {
RoadTrace { let mut trace = Self::from_structs(ip, ua, reg, loc, end);
region: reg.id, trace.error = Some(err);
location: loc.id, return trace;
destination: end.id,
error: Some(err),
}
} }
} }
@ -47,7 +55,7 @@ pub struct RoadMetrics {
pub recent_errors: VecDeque<RoadTrace>, pub recent_errors: VecDeque<RoadTrace>,
} }
const MAX_TRACE_COUNT: usize = 10; const MAX_TRACE_COUNT: usize = 32;
impl RoadMetrics { impl RoadMetrics {
pub fn new() -> RoadMetrics { pub fn new() -> RoadMetrics {
@ -67,26 +75,35 @@ impl RoadMetrics {
} }
} }
pub fn add_success_request(&mut self, reg: Region, loc: Location, end: Destination) { pub fn add_success_request(
&mut self,
ip: String,
ua: String,
reg: Region,
loc: Location,
end: Destination,
) {
self.requests_count += 1; self.requests_count += 1;
self.recent_successes self.recent_successes
.push_back(RoadTrace::from_structs(reg, loc, end)); .push_back(RoadTrace::from_structs(ip, ua, reg, loc, end));
if self.recent_successes.len() > MAX_TRACE_COUNT { if self.recent_successes.len() > MAX_TRACE_COUNT {
self.recent_successes.pop_front(); self.recent_successes.pop_front();
} }
} }
pub fn add_faliure_request( pub fn add_failure_request(
&mut self, &mut self,
ip: String,
ua: String,
reg: Region, reg: Region,
loc: Location, loc: Location,
end: Destination, end: Destination,
err: String, // For some reason error is rarely clonable, so we use preformatted message err: String, // For some reason error is rarely cloneable, so we use preformatted message
) { ) {
self.requests_count += 1; self.requests_count += 1;
self.failures_count += 1; self.failures_count += 1;
self.recent_errors self.recent_errors
.push_back(RoadTrace::from_structs_with_error(reg, loc, end, err)); .push_back(RoadTrace::from_structs_with_error(ip, ua, reg, loc, end, err));
if self.recent_errors.len() > MAX_TRACE_COUNT { if self.recent_errors.len() > MAX_TRACE_COUNT {
self.recent_errors.pop_front(); self.recent_errors.pop_front();
} }

View File

@ -1,7 +1,9 @@
use http::Method; use actix_web::http::header::{ContentType, HeaderMap};
use poem::http::{HeaderMap, Uri}; use actix_web::http::{Method, StatusCode, Uri};
use regex::Regex; use regex::Regex;
use wildmatch::WildMatch; use wildmatch::WildMatch;
use actix_web::{error, HttpResponse};
use derive_more::{Display};
use crate::warden::WardenInstance; use crate::warden::WardenInstance;
@ -10,12 +12,52 @@ use self::{
metrics::RoadMetrics, metrics::RoadMetrics,
}; };
pub mod browser;
pub mod config; pub mod config;
pub mod loader; pub mod loader;
pub mod metrics; pub mod metrics;
pub mod responder; pub mod responder;
pub mod route; pub mod route;
pub mod server;
#[derive(Debug, Display)]
pub enum ProxyError {
#[display(fmt = "Remote gateway issue")]
BadGateway,
#[display(fmt = "No configured able to process this request")]
NoGateway,
#[display(fmt = "Not found")]
NotFound,
#[display(fmt = "Only accepts method GET")]
MethodGetOnly,
#[display(fmt = "Invalid request path")]
InvalidRequestPath,
#[display(fmt = "Upstream does not support protocol you used")]
NotImplemented,
}
impl error::ResponseError for ProxyError {
fn status_code(&self) -> StatusCode {
match *self {
ProxyError::BadGateway => StatusCode::BAD_GATEWAY,
ProxyError::NoGateway => StatusCode::NOT_FOUND,
ProxyError::NotFound => StatusCode::NOT_FOUND,
ProxyError::MethodGetOnly => StatusCode::METHOD_NOT_ALLOWED,
ProxyError::InvalidRequestPath => StatusCode::BAD_REQUEST,
ProxyError::NotImplemented => StatusCode::NOT_IMPLEMENTED,
}
}
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RoadInstance { pub struct RoadInstance {
@ -38,7 +80,7 @@ impl RoadInstance {
pub fn filter( pub fn filter(
&self, &self,
uri: &Uri, uri: &Uri,
method: Method, method: &Method,
headers: &HeaderMap, headers: &HeaderMap,
) -> Option<(&Region, &Location)> { ) -> Option<(&Region, &Location)> {
self.regions.iter().find_map(|region| { self.regions.iter().find_map(|region| {

View File

@ -1,117 +1,51 @@
use futures_util::{SinkExt, StreamExt};
use http::{header, request::Builder, HeaderMap, Method, StatusCode, Uri};
use lazy_static::lazy_static;
use poem::{
web::{websocket::WebSocket, StaticFileRequest},
Body, Error, FromRequest, IntoResponse, Request, Response,
};
use std::{ use std::{
ffi::OsStr, ffi::OsStr,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc,
}; };
use tokio::sync::RwLock; use actix_files::{NamedFile};
use tokio_tungstenite::connect_async; use actix_proxy::IntoHttpResponse;
use actix_web::{HttpRequest, HttpResponse, web};
use super::browser::{DirectoryTemplate, FileRef}; use actix_web::http::Method;
use awc::Client;
lazy_static! { use tracing::log::warn;
pub static ref CLIENT: reqwest::Client = reqwest::Client::new(); use crate::proxies::ProxyError;
}
pub async fn repond_websocket(req: Builder, ws: WebSocket) -> Response {
ws.on_upgrade(move |socket| async move {
let (mut clientsink, mut clientstream) = socket.split();
// Start connection to server
let (serversocket, _) = connect_async(req.body(()).unwrap()).await.unwrap();
let (mut serversink, mut serverstream) = serversocket.split();
let client_live = Arc::new(RwLock::new(true));
let server_live = client_live.clone();
tokio::spawn(async move {
while let Some(Ok(msg)) = clientstream.next().await {
if (serversink.send(msg.into()).await).is_err() {
break;
};
if !*client_live.read().await {
break;
};
}
*client_live.write().await = false;
});
// Relay server messages to the client
tokio::spawn(async move {
while let Some(Ok(msg)) = serverstream.next().await {
if (clientsink.send(msg.into()).await).is_err() {
break;
};
if !*server_live.read().await {
break;
};
}
*server_live.write().await = false;
});
})
.into_response()
}
pub async fn respond_hypertext( pub async fn respond_hypertext(
uri: String, uri: String,
ori: &Uri, req: HttpRequest,
req: &Request, client: web::Data<Client>,
method: Method, ) -> Result<HttpResponse, ProxyError> {
body: Body, let ip = req.peer_addr().unwrap().ip().to_string();
headers: &HeaderMap,
) -> Result<Response, Error> {
let ip = req.remote_addr().to_string();
let proto = req.uri().scheme_str().unwrap(); let proto = req.uri().scheme_str().unwrap();
let host = req.uri().host().unwrap(); let host = req.uri().host().unwrap();
let mut headers = headers.clone(); let mut headers = req.headers().clone();
headers.insert("Server", "RoadSign".parse().unwrap()); headers.insert("Server".parse().unwrap(), "RoadSign".parse().unwrap());
headers.insert("X-Forward-For", ip.parse().unwrap()); headers.insert("X-Forward-For".parse().unwrap(), ip.parse().unwrap());
headers.insert("X-Forwarded-Proto", proto.parse().unwrap()); headers.insert("X-Forwarded-Proto".parse().unwrap(), proto.parse().unwrap());
headers.insert("X-Forwarded-Host", host.parse().unwrap()); headers.insert("X-Forwarded-Host".parse().unwrap(), host.parse().unwrap());
headers.insert("X-Real-IP", ip.parse().unwrap()); headers.insert("X-Real-IP".parse().unwrap(), ip.parse().unwrap());
headers.insert( headers.insert(
"Forwarded", "Forwarded".parse().unwrap(),
format!("by={};for={};host={};proto={}", ip, ip, host, proto) format!("by={};for={};host={};proto={}", ip, ip, host, proto)
.parse() .parse()
.unwrap(), .unwrap(),
); );
let res = CLIENT let res = client.get(uri).send().await;
.request(method, uri + ori.path() + ori.query().unwrap_or(""))
.headers(headers.clone())
.body(body.into_bytes().await.unwrap())
.send()
.await;
match res { return match res {
Ok(result) => { Ok(result) => {
let mut res = Response::default(); let mut res = result.into_http_response();
res.extensions().clone_from(&result.extensions()); res.headers_mut().insert("Server".parse().unwrap(), "RoadSign".parse().unwrap());
result.headers().iter().for_each(|(key, val)| {
res.headers_mut().insert(key, val.to_owned());
});
res.headers_mut()
.insert("Server", "RoadSign".parse().unwrap());
res.set_status(result.status());
res.set_version(result.version());
res.set_body(result.bytes().await.unwrap());
Ok(res) Ok(res)
} }
Err(error) => Err(Error::from_string( Err(error) => {
error.to_string(), warn!("Proxy got a upstream issue... {:?}", error);
error.status().unwrap_or(StatusCode::BAD_GATEWAY), Err(ProxyError::BadGateway)
)),
} }
};
} }
pub struct StaticResponderConfig { pub struct StaticResponderConfig {
@ -126,14 +60,10 @@ pub struct StaticResponderConfig {
pub async fn respond_static( pub async fn respond_static(
cfg: StaticResponderConfig, cfg: StaticResponderConfig,
method: Method, req: HttpRequest,
req: &Request, ) -> Result<HttpResponse, ProxyError> {
) -> Result<Response, Error> { if req.method() != Method::GET {
if method != Method::GET { return Err(ProxyError::MethodGetOnly);
return Err(Error::from_string(
"This destination only support GET request.",
StatusCode::METHOD_NOT_ALLOWED,
));
} }
let path = req let path = req
@ -142,9 +72,12 @@ pub async fn respond_static(
.trim_start_matches('/') .trim_start_matches('/')
.trim_end_matches('/'); .trim_end_matches('/');
let path = percent_encoding::percent_decode_str(path) let path = match percent_encoding::percent_decode_str(path).decode_utf8() {
.decode_utf8() Ok(val) => val,
.map_err(|_| Error::from_status(StatusCode::NOT_FOUND))?; Err(_) => {
return Err(ProxyError::NotFound);
}
};
let base_path = cfg.uri.parse::<PathBuf>().unwrap(); let base_path = cfg.uri.parse::<PathBuf>().unwrap();
let mut file_path = base_path.clone(); let mut file_path = base_path.clone();
@ -159,7 +92,7 @@ pub async fn respond_static(
} }
if !file_path.starts_with(cfg.uri) { if !file_path.starts_with(cfg.uri) {
return Err(Error::from_status(StatusCode::FORBIDDEN)); return Err(ProxyError::InvalidRequestPath);
} }
if !file_path.exists() { if !file_path.exists() {
@ -172,87 +105,30 @@ pub async fn respond_static(
file_path.pop(); file_path.pop();
file_path.push((file_name + &suffix).as_str()); file_path.push((file_name + &suffix).as_str());
if file_path.is_file() { if file_path.is_file() {
return Ok(StaticFileRequest::from_request_without_body(req) return Ok(NamedFile::open(file_path).unwrap().into_response(&req));
.await?
.create_response(&file_path, cfg.utf8)?
.into_response());
} }
} }
if let Some(file) = cfg.fallback { if let Some(file) = cfg.fallback {
let fallback_path = base_path.join(file); let fallback_path = base_path.join(file);
if fallback_path.is_file() { if fallback_path.is_file() {
return Ok(StaticFileRequest::from_request_without_body(req) return Ok(NamedFile::open(fallback_path).unwrap().into_response(&req));
.await?
.create_response(&fallback_path, cfg.utf8)?
.into_response());
} }
} }
return Err(Error::from_status(StatusCode::NOT_FOUND));
}
if file_path.is_file() { return Err(ProxyError::NotFound);
Ok(StaticFileRequest::from_request_without_body(req) }
.await?
.create_response(&file_path, cfg.utf8)? return if file_path.is_file() {
.into_response()) Ok(NamedFile::open(file_path).unwrap().into_response(&req))
} else { } else {
if cfg.with_slash
&& !req.original_uri().path().ends_with('/')
&& (cfg.index.is_some() || cfg.browse)
{
let redirect_to = format!("{}/", req.original_uri().path());
return Ok(Response::builder()
.status(StatusCode::FOUND)
.header(header::LOCATION, redirect_to)
.finish());
}
if let Some(index_file) = &cfg.index { if let Some(index_file) = &cfg.index {
let index_path = file_path.join(index_file); let index_path = file_path.join(index_file);
if index_path.is_file() { if index_path.is_file() {
return Ok(StaticFileRequest::from_request_without_body(req) return Ok(NamedFile::open(index_path).unwrap().into_response(&req));
.await?
.create_response(&index_path, cfg.utf8)?
.into_response());
} }
} }
if cfg.browse { return Err(ProxyError::NotFound);
let read_dir = file_path
.read_dir()
.map_err(|_| Error::from_status(StatusCode::FORBIDDEN))?;
let mut template = DirectoryTemplate {
path: &path,
files: Vec::new(),
}; };
for res in read_dir {
let entry = res.map_err(|_| Error::from_status(StatusCode::FORBIDDEN))?;
if let Some(filename) = entry.file_name().to_str() {
let mut base_url = req.original_uri().path().to_string();
if !base_url.ends_with('/') {
base_url.push('/');
}
let filename_url = percent_encoding::percent_encode(
filename.as_bytes(),
percent_encoding::NON_ALPHANUMERIC,
);
template.files.push(FileRef {
url: format!("{base_url}{filename_url}"),
filename: filename.to_string(),
is_dir: entry.path().is_dir(),
});
}
}
let html = template.render();
Ok(Response::builder()
.header(header::CONTENT_TYPE, mime::TEXT_HTML_UTF_8.as_ref())
.body(Body::from_string(html)))
} else {
Err(Error::from_status(StatusCode::NOT_FOUND))
}
}
} }

View File

@ -1,10 +1,6 @@
use http::Method; use actix_web::{HttpRequest, HttpResponse, ResponseError, web};
use poem::{ use actix_web::http::header;
handler, use awc::Client;
http::{HeaderMap, StatusCode, Uri},
web::websocket::WebSocket,
Body, Error, FromRequest, IntoResponse, Request, Response, Result,
};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use crate::{ use crate::{
@ -14,23 +10,14 @@ use crate::{
}, },
ROAD, ROAD,
}; };
use crate::proxies::ProxyError;
#[handler] pub async fn handle(req: HttpRequest, client: web::Data<Client>) -> HttpResponse {
pub async fn handle(
req: &Request,
uri: &Uri,
headers: &HeaderMap,
method: Method,
body: Body,
) -> Result<impl IntoResponse, Error> {
let readable_app = ROAD.lock().await; let readable_app = ROAD.lock().await;
let (region, location) = match readable_app.filter(uri, method.clone(), headers) { let (region, location) = match readable_app.filter(req.uri(), req.method(), req.headers()) {
Some(val) => val, Some(val) => val,
None => { None => {
return Err(Error::from_string( return ProxyError::NoGateway.error_response();
"There are no region be able to respone this request.",
StatusCode::NOT_FOUND,
))
} }
}; };
@ -41,58 +28,26 @@ pub async fn handle(
async fn forward( async fn forward(
end: &Destination, end: &Destination,
req: &Request, req: HttpRequest,
ori: &Uri, client: web::Data<Client>,
headers: &HeaderMap, ) -> Result<HttpResponse, ProxyError> {
method: Method,
body: Body,
) -> Result<Response, Error> {
// Handle websocket
if let Ok(ws) = WebSocket::from_request_without_body(req).await {
// Get uri
let Ok(uri) = end.get_websocket_uri() else {
return Err(Error::from_string(
"This destination was not support websockets.",
StatusCode::NOT_IMPLEMENTED,
));
};
// Build request
let mut ws_req = http::Request::builder().uri(&uri);
for (key, value) in headers.iter() {
ws_req = ws_req.header(key, value);
}
// Start the websocket connection
return Ok(responder::repond_websocket(ws_req, ws).await);
}
// Handle normal web request // Handle normal web request
match end.get_type() { match end.get_type() {
DestinationType::Hypertext => { DestinationType::Hypertext => {
let Ok(uri) = end.get_hypertext_uri() else { let Ok(uri) = end.get_hypertext_uri() else {
return Err(Error::from_string( return Err(ProxyError::NotImplemented);
"This destination was not support web requests.",
StatusCode::NOT_IMPLEMENTED,
));
}; };
responder::respond_hypertext(uri, ori, req, method, body, headers).await responder::respond_hypertext(uri, req, client).await
} }
DestinationType::StaticFiles => { DestinationType::StaticFiles => {
let Ok(cfg) = end.get_static_config() else { let Ok(cfg) = end.get_static_config() else {
return Err(Error::from_string( return Err(ProxyError::NotImplemented);
"This destination was not support static files.",
StatusCode::NOT_IMPLEMENTED,
));
}; };
responder::respond_static(cfg, method, req).await responder::respond_static(cfg, req).await
} }
_ => Err(Error::from_string( _ => Err(ProxyError::NotImplemented)
"Unsupported destination protocol.",
StatusCode::NOT_IMPLEMENTED,
)),
} }
} }
@ -100,23 +55,32 @@ pub async fn handle(
let loc = location.clone(); let loc = location.clone();
let end = destination.clone(); let end = destination.clone();
match forward(&end, req, uri, headers, method, body).await { let ip = match req.peer_addr() {
None => "unknown".to_string(),
Some(val) => val.ip().to_string()
};
let ua = match req.headers().get(header::USER_AGENT) {
None => "unknown".to_string(),
Some(val) => val.to_str().unwrap().to_string(),
};
match forward(&end, req, client).await {
Ok(resp) => { Ok(resp) => {
tokio::spawn(async move { tokio::spawn(async move {
let writable_app = &mut ROAD.lock().await; let writable_app = &mut ROAD.lock().await;
writable_app.metrics.add_success_request(reg, loc, end); writable_app.metrics.add_success_request(ip, ua, reg, loc, end);
}); });
Ok(resp) resp
} }
Err(err) => { Err(resp) => {
let message = format!("{:}", err); let message = resp.to_string();
tokio::spawn(async move { tokio::spawn(async move {
let writable_app = &mut ROAD.lock().await; let writable_app = &mut ROAD.lock().await;
writable_app writable_app
.metrics .metrics
.add_faliure_request(reg, loc, end, message); .add_failure_request(ip, ua, reg, loc, end, message);
}); });
Err(err) resp.error_response()
} }
} }
} }

39
src/proxies/server.rs Normal file
View File

@ -0,0 +1,39 @@
use std::error;
use actix_web::{App, HttpServer, web};
use actix_web::dev::Server;
use actix_web::middleware::Logger;
use awc::Client;
use crate::config::CFG;
use crate::proxies::route;
use crate::server::ServerBindConfig;
use crate::tls::{load_certificates, use_rustls};
pub async fn build_proxies() -> Result<Vec<Server>, Box<dyn error::Error>> {
load_certificates().await?;
let cfg = CFG
.read()
.await
.get::<Vec<ServerBindConfig>>("proxies.bind")?;
let mut tasks = Vec::new();
for item in cfg {
tasks.push(build_single_proxy(item)?);
}
Ok(tasks)
}
pub fn build_single_proxy(cfg: ServerBindConfig) -> Result<Server, Box<dyn error::Error>> {
let server = HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.app_data(web::Data::new(Client::default()))
.route("/", web::to(route::handle))
});
if cfg.tls {
Ok(server.bind_rustls_0_22(cfg.addr, use_rustls()?)?.run())
} else {
Ok(server.bind(cfg.addr)?.run())
}
}

7
src/server.rs Normal file
View File

@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct ServerBindConfig {
pub addr: String,
pub tls: bool,
}

View File

@ -1,19 +1,15 @@
use poem_openapi::OpenApi; use actix_web::{Scope, web};
use crate::sideload::overview::get_overview;
use crate::sideload::regions::list_region;
pub mod overview; mod overview;
pub mod regions; mod regions;
pub mod server;
pub struct SideloadApi; static ROOT: &str = "";
#[OpenApi] pub fn service() -> Scope {
impl SideloadApi { web::scope("/cgi")
#[oai(path = "/", method = "get")] .route(ROOT, web::get().to(get_overview))
async fn index(&self) -> overview::OverviewResponse { .route("/regions", web::get().to(list_region))
overview::index().await
}
#[oai(path = "/regions", method = "get")]
async fn regions_index(&self) -> regions::RegionResponse {
regions::index().await
}
} }

View File

@ -1,46 +1,23 @@
use poem_openapi::{payload::Json, ApiResponse, Object}; use actix_web::web;
use serde::Serialize;
use crate::proxies::config::{Destination, Location};
use crate::proxies::metrics::RoadTrace;
use crate::ROAD;
use crate::{ #[derive(Debug, Clone, PartialEq, Serialize)]
proxies::{
config::{Destination, Location},
metrics::RoadTrace,
},
ROAD,
};
#[derive(ApiResponse)]
pub enum OverviewResponse {
/// Return the overview data.
#[oai(status = 200)]
Ok(Json<OverviewData>),
}
#[derive(Debug, Object, Clone, PartialEq)]
pub struct OverviewData { pub struct OverviewData {
/// Loaded regions count
#[oai(read_only)]
regions: usize, regions: usize,
/// Loaded locations count
#[oai(read_only)]
locations: usize, locations: usize,
/// Loaded destnations count
#[oai(read_only)]
destinations: usize, destinations: usize,
/// Recent requests count
requests_count: u64, requests_count: u64,
/// Recent requests success count failures_count: u64,
faliures_count: u64,
/// Recent requests falied count
successes_count: u64, successes_count: u64,
/// Recent requests success rate
success_rate: f64, success_rate: f64,
/// Recent successes
recent_successes: Vec<RoadTrace>, recent_successes: Vec<RoadTrace>,
/// Recent errors
recent_errors: Vec<RoadTrace>, recent_errors: Vec<RoadTrace>,
} }
pub async fn index() -> OverviewResponse { pub async fn get_overview() -> web::Json<OverviewData> {
let locked_app = ROAD.lock().await; let locked_app = ROAD.lock().await;
let regions = locked_app.regions.clone(); let regions = locked_app.regions.clone();
let locations = regions let locations = regions
@ -51,13 +28,13 @@ pub async fn index() -> OverviewResponse {
.iter() .iter()
.flat_map(|item| item.destinations.clone()) .flat_map(|item| item.destinations.clone())
.collect::<Vec<Destination>>(); .collect::<Vec<Destination>>();
OverviewResponse::Ok(Json(OverviewData { web::Json(OverviewData {
regions: regions.len(), regions: regions.len(),
locations: locations.len(), locations: locations.len(),
destinations: destinations.len(), destinations: destinations.len(),
requests_count: locked_app.metrics.requests_count, requests_count: locked_app.metrics.requests_count,
successes_count: locked_app.metrics.requests_count - locked_app.metrics.failures_count, successes_count: locked_app.metrics.requests_count - locked_app.metrics.failures_count,
faliures_count: locked_app.metrics.failures_count, failures_count: locked_app.metrics.failures_count,
success_rate: locked_app.metrics.get_success_rate(), success_rate: locked_app.metrics.get_success_rate(),
recent_successes: locked_app recent_successes: locked_app
.metrics .metrics
@ -71,5 +48,5 @@ pub async fn index() -> OverviewResponse {
.clone() .clone()
.into_iter() .into_iter()
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
})) })
} }

View File

@ -1,25 +1,9 @@
use poem_openapi::{payload::Json, ApiResponse}; use actix_web::web;
use crate::proxies::config::Region;
use crate::ROAD;
use crate::{proxies::config::Region, ROAD}; pub async fn list_region() -> web::Json<Vec<Region>> {
#[derive(ApiResponse)]
pub enum RegionResponse {
/// Return the region data.
#[oai(status = 200)]
Ok(Json<Region>),
/// Return the list of region data.
#[oai(status = 200)]
OkMany(Json<Vec<Region>>),
/// Return the region data after created.
#[oai(status = 201)]
Created(Json<Region>),
/// Return was not found.
#[oai(status = 404)]
NotFound,
}
pub async fn index() -> RegionResponse {
let locked_app = ROAD.lock().await; let locked_app = ROAD.lock().await;
RegionResponse::OkMany(Json(locked_app.regions.clone())) web::Json(locked_app.regions.clone())
} }

35
src/sideload/server.rs Normal file
View File

@ -0,0 +1,35 @@
use std::error;
use actix_web::dev::Server;
use actix_web::{App, HttpServer};
use actix_web_httpauth::extractors::AuthenticationError;
use actix_web_httpauth::headers::www_authenticate::basic::Basic;
use actix_web_httpauth::middleware::HttpAuthentication;
use crate::sideload;
pub async fn build_sideload() -> Result<Server, Box<dyn error::Error>> {
Ok(
HttpServer::new(|| {
App::new()
.wrap(HttpAuthentication::basic(|req, credentials| async move {
let password = match crate::config::CFG
.read()
.await
.get_string("secret") {
Ok(val) => val,
Err(_) => return Err((AuthenticationError::new(Basic::new()).into(), req))
};
if credentials.password().unwrap_or("") != password {
Err((AuthenticationError::new(Basic::new()).into(), req))
} else {
Ok(req)
}
}))
.service(sideload::service())
}).bind(
crate::config::CFG
.read()
.await
.get_string("sideload.bind_addr")?
)?.workers(1).run()
)
}

76
src/tls.rs Normal file
View File

@ -0,0 +1,76 @@
use std::fs::File;
use std::{error};
use std::io::BufReader;
use std::sync::Arc;
use config::ConfigError;
use lazy_static::lazy_static;
use rustls::crypto::ring::sign::RsaSigningKey;
use rustls::server::{ClientHello, ResolvesServerCert};
use rustls::sign::CertifiedKey;
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use wildmatch::WildMatch;
lazy_static! {
static ref CERTS: Mutex<Vec<CertificateConfig>> = Mutex::new(Vec::new());
}
#[derive(Debug)]
struct ProxyCertResolver;
impl ResolvesServerCert for ProxyCertResolver {
fn resolve(&self, handshake: ClientHello) -> Option<Arc<CertifiedKey>> {
let domain = handshake.server_name()?;
let certs = CERTS.lock().unwrap();
for cert in certs.iter() {
if WildMatch::new(cert.domain.as_str()).matches(domain) {
return match cert.clone().load() {
Ok(val) => Some(val),
Err(_) => None
};
}
}
None
}
}
#[derive(Clone, Serialize, Deserialize)]
struct CertificateConfig {
pub domain: String,
pub certs: String,
pub key: String,
}
impl CertificateConfig {
pub fn load(self) -> Result<Arc<CertifiedKey>, Box<dyn error::Error>> {
let certs =
rustls_pemfile::certs(&mut BufReader::new(&mut File::open(self.certs)?))
.collect::<Result<Vec<_>, _>>()?;
let key =
rustls_pemfile::private_key(&mut BufReader::new(&mut File::open(self.key)?))?
.unwrap();
let sign = RsaSigningKey::new(&key)?;
Ok(Arc::new(CertifiedKey::new(certs, Arc::new(sign))))
}
}
pub async fn load_certificates() -> Result<(), ConfigError> {
let certs = crate::config::CFG
.read()
.await
.get::<Vec<CertificateConfig>>("certificates")?;
CERTS.lock().unwrap().clone_from(&certs);
Ok(())
}
pub fn use_rustls() -> Result<rustls::ServerConfig, ConfigError> {
Ok(
rustls::ServerConfig::builder()
.with_no_client_auth()
.with_cert_resolver(Arc::new(ProxyCertResolver))
)
}

View File

@ -2,10 +2,9 @@ pub mod runner;
use std::collections::HashMap; use std::collections::HashMap;
use futures_util::lock::Mutex;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use poem_openapi::Object;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::proxies::config::Region; use crate::proxies::config::Region;
@ -63,7 +62,7 @@ impl Default for WardenInstance {
} }
} }
#[derive(Debug, Object, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Application { pub struct Application {
pub id: String, pub id: String,
pub exe: String, pub exe: String,

View File

@ -1,12 +1,12 @@
use std::{borrow::BorrowMut, collections::HashMap, io}; use std::{borrow::BorrowMut, collections::HashMap, io};
use super::Application; use super::Application;
use futures_util::lock::Mutex;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use tokio::{ use tokio::{
io::{AsyncBufReadExt, BufReader}, io::{AsyncBufReadExt, BufReader},
process::{Child, Command}, process::{Child, Command},
}; };
use tokio::sync::Mutex;
lazy_static! { lazy_static! {
static ref STDOUT: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new()); static ref STDOUT: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
:root{--bs-body-font-family: "IBM Plex Serif", "Noto Serif SC", sans-serif !important}html,body{font-family:var(--bs-body-font-family)}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:100;src:url(/_astro/ibm-plex-serif-v19-latin-100.6qNbweSL.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:100;src:url(/_astro/ibm-plex-serif-v19-latin-100italic.E22nrI7z.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:200;src:url(/_astro/ibm-plex-serif-v19-latin-200.GFXE_YJc.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:200;src:url(/_astro/ibm-plex-serif-v19-latin-200italic.pJK4yaaG.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:300;src:url(/_astro/ibm-plex-serif-v19-latin-300.RVbRgkxX.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:300;src:url(/_astro/ibm-plex-serif-v19-latin-300italic.ZdSVgmcR.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:400;src:url(/_astro/ibm-plex-serif-v19-latin-regular.HRmMD3sQ.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:400;src:url(/_astro/ibm-plex-serif-v19-latin-italic.MiJiQVsi.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:500;src:url(/_astro/ibm-plex-serif-v19-latin-500.xAA_w-Ac.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:500;src:url(/_astro/ibm-plex-serif-v19-latin-500italic.Unq84pJ7.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:600;src:url(/_astro/ibm-plex-serif-v19-latin-600.cuuqzllG.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:600;src:url(/_astro/ibm-plex-serif-v19-latin-600italic.vDhUog1q.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:700;src:url(/_astro/ibm-plex-serif-v19-latin-700.yX9JjmCp.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:700;src:url(/_astro/ibm-plex-serif-v19-latin-700italic.QM1RA0vx.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:200;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-200.g4OBZhIi.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:300;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-300.yFtdUYoh.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:400;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-regular.9muiKgFz.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:500;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-500.exkAspFQ.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:600;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-600.4n6uFOXj.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:700;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-700.HyiB9Pzv.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:900;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-900.ERSRy_0V.woff2) format("woff2")}.astro-route-announcer{position:absolute;left:0;top:0;clip:rect(0 0 0 0);-webkit-clip-path:inset(50%);clip-path:inset(50%);overflow:hidden;white-space:nowrap;width:1px;height:1px}.h-fullpage{height:calc(100vh - 64px)}.max-h-fullpage{max-height:calc(100vh - 64px)}.mt-header{margin-top:64px}.top-header{top:64px}html{overflow-x:hidden!important;overflow-y:auto!important}@keyframes astroFadeInOut{0%{opacity:1}to{opacity:0}}@keyframes astroFadeIn{0%{opacity:0}}@keyframes astroFadeOut{to{opacity:0}}@keyframes astroSlideFromRight{0%{transform:translate(100%)}}@keyframes astroSlideFromLeft{0%{transform:translate(-100%)}}@keyframes astroSlideToRight{to{transform:translate(100%)}}@keyframes astroSlideToLeft{to{transform:translate(-100%)}}@media (prefers-reduced-motion){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}[data-astro-transition-scope]{animation:none!important}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
index.html
assets/

View File

@ -0,0 +1,21 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
<title>SmartSheep Logo</title>
<defs>
<image width="124" height="198" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHwAAADGCAMAAAAnkRSfAAAAAXNSR0IB2cksfwAAAq9QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////J9QBRAAAAOV0Uk5TAA0WEiFfjMLMr40/BRdmtuz//vbQYAQmpfLdQAGovyURj/iOD0r07loDGJfbPDTf/bhd94QLdPrrVQLWcPmtHU31egknzehRg9Ev1xnzbgYHhfzmTMorSOSbFJloKsniReXDKG/7kQ6m8WMw3ue7I4cKNdTZOFTqsR5/Hzra0zZY7al4Ir5SPWHvnpbG4MRrlKJs4UfjvKos3Eu1DICGCBq0XjHV6YqCG7lZM89cEJJ72EQ5WztxJC1OfGl+T315dnVzkHKVbZ9no2ViwFZDNzIuKSAcFfBGy4tQd7Onk0K3PqxTmt0ZReEAAAZZSURBVHic7dzpX1RVGAfwyyKMyHYuyDqgjLKICjcIBodNhEoJFAY0wCBEJyTCBWUx0WRJU1wqlKIwTLDEUoMiyFJssWyjtBAr2/1DGvywzzPDeXGehzf8/oHv594z985znnPOlaTRWFlbW9tIMxLbOXb2KtVch3mO5LSTs4srG47s5j7fg9b29PJmY5F9fNWEtrUfmxTZfwGZvTBAw6ZEs2gxER4YNNU2XnxwyBIKO3SpqT3ML1tOgIeFgzhjykO26HiEtxmcsciHo5DxaK1ZnLGYFbqZwxmLDcPE4+It4kxJSMTDVwJP2uQkrUrGwlMemQ5n7NHHkIY+avX0OGNrUnH01Md5dK1XGooe58ajs/S16xDwjEw9l86yshH+bNUOlp/1scjrN4jXpSdy+HSmz80Tr1tvnOZdMxbvJxHqrPwChZN/qlC8rg7cxKmzos3i+XVbpn3VjsTwdLF4Pm2rgZMveQahzip1lzn5Z33F67rsMk5d2bZdPJ+8info43fsFM8nzuV84bLgCIQ/2/JdnEMv764Qr+sqqzh5pXqPeN4qM5Lz3kc+lyGe3+vCPfQhCH+2Nft4n/r9z4vXdQdqOXl9Xah4HphBm4lbPUJPpeEF3qGPOSheVx/y4X3qVaXieccXD3NevOEIQj8tr5G3zkqPQ6izPI/yPnZZ2eJ1te8xTl05ni+ezzhRwslrX7ISzzu9zDm/YN6vINRZ+et5h76pULyuPnmK96lvxqizotM5Lz7eDqGflfYqd4ntjDD0r63h1FnLPPG6+nXe2ZXyBkKd1Xo6iZPXvtkmnj/zFu/M9vBZhDqrvYNTZx014nld5TlOXUlAqLMWv83XzWJM8w5CiX2+k7vOuoAwu3o3lnd6816Y+KH3uOjKefF6L4R+1qXLvCV20vsYsyt/zqde7uoWr0sfLOO8+KAPEXRHZ84SOx5hemGcXeXy1VlJGNdunF0d53rs3J1QdKknnIPXX8TBpbaPzK8UjqUICZckm+lbmUGIuwR6C6a593IfHi6pu5ss6x8j4pK084rFVuYnqLhxYp1g4Y17Ghk3TqxVZof+U3RcWnLVXIl9DR83ltiZcJ11hQI3TqxbILyfBpfsILyeCHeA8OtE+CII/4wID4Dwz4nwLyD8SyL8MoTHEeE3IHwLEd4I4V8R4Rsh/AQRngvh2H+po/kawm8S4d9A+LdEeB2Ef0eEJ0D4fCK8GsLnEOEuEB5BhDdDOM5U0TTfQ3gIEV4E4QeI8KMQ/gMRXgDhlUS4CsJROiNAwJWBq0T4fgjHnaWOJxbCERY9wewGbDmQCIeag/JJItwHwjF6oFCgBok8QIUDHQIZYUsbmBwAVw4R4VnQbe8hwqH9fXINEQ4tfyk/EuEx0Jgj7KYC4wr92suJ8GBozH8iwqHFD307EQ7tcFFuzSCux9grD0QN4rdpcB20u8TgSYN7QH13Qy8NngFtrtA20ODJUNvZ8DMNHgVt39X+QoMXQ3g8zpEgk9hAS+vxCPs3oAyC+CAN7gThGqJj16HQsrbmEg1+HsLdWmnwRBBHOA8BZS+0iScJYacYlAZoPTud6Jz9dhAnOWotSSnQbQ8iOmOfD60ll9DY0oaZxPug236HCC+FfnC1RHg7dNup8DAIjyHCK6CtC1T48pnEhyC8ighfDj1q4UR4KlTJ3CXCbaGGkBcRLvmb2soFKvym6X1vQjhSDKfNdKnhV9zPZ0xMxNSpYixVX8KYtoDJNWTVEJ0tSbqAiae+an+jtI2pbxnh5aBmqoWlsXjY/H73lEbj1nTvIMLhdY409Pb2LpwReTazmc1sZjMbsdm8IsDPb2v/ENIhEktJi1ZphmerSvA9sqp5NIkTPhPk/QdR63MkNV2TZgx1RB3AB0nbN2W+4keI/zl1khxJtTNKkspNV+87EU4Lwrlh2h0IQjgfDSZvl4nN5NVE+G1oRXEbEV4BNQE7iPBCqCGUQ4SvhPBzRPhfEF5GhA9AeBYRDl45Fd4NNX43EeEDEN5EhPdA7c8uIrwP2jlgT4TvgdqfR4jw4r9NbQPVSRIp2vTlXkbWBUwx3eycS9eSuj51ETv4HzJbOtM5+SXndo2yfL1lP1GPDMD4dqz55P873ng+toNop8hYBs9W33lA+ywtJP5A/XAW/Nd//37j2gFreno4Ua2treOj/T+0HjP//7ac7AAAAABJRU5ErkJggg=="/>
<image width="122" height="142" id="img2" href="data:image/png;base64,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"/>
</defs>
<style>
.s0 { fill: #ffffff;stroke: #000000;stroke-miterlimit:100;stroke-width: 56 }
.s1 { fill: #4750a3;stroke: #000000;stroke-miterlimit:100;stroke-width: 56 }
</style>
<path id="Wool" fill-rule="evenodd" class="s0" d="m128 608.4c0 95.9 77.4 173.6 172.8 173.6h441.6c84.8 0 153.6-69.1 153.6-154.3 0-74.6-52.8-136.9-122.9-151.1 4.9-12.9 7.7-27 7.7-41.7 0-63.9-51.6-115.8-115.2-115.8-23.6 0-45.7 7.3-64 19.6-33.2-57.9-95.2-96.7-166.4-96.7-106.1 0-192 86.3-192 192.9 0 3.2 0.1 6.5 0.2 9.7-67.2 23.8-115.4 88.1-115.4 163.8z"/>
<g id="Crystal">
<path id="Crystal" class="s1" d="m699 224l138.6 80v160l-138.6 80-138.6-80v-160z"/>
<use id="Highlight" href="#img1" x="688" y="255"/>
</g>
<g id="Horn">
</g>
<g id="Face">
<use id="Slime" href="#img2" x="233" y="538"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://smartsheep.studio/</loc></url><url><loc>https://smartsheep.studio/events/</loc></url><url><loc>https://smartsheep.studio/posts/</loc></url></urlset>

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><sitemap><loc>https://smartsheep.studio/sitemap-0.xml</loc></sitemap></sitemapindex>

View File

@ -0,0 +1,3 @@
const onRequest = undefined;
export { onRequest };

View File

@ -0,0 +1,6 @@
export { renderers } from '../renderers.mjs';
export { onRequest } from '../_empty-middleware.mjs';
const page = () => import('./pages/_slug__TUDhKBhQ.mjs').then(n => n.c);
export { page };

View File

@ -0,0 +1,6 @@
export { renderers } from '../renderers.mjs';
export { onRequest } from '../_empty-middleware.mjs';
const page = () => import('./pages/_slug__TUDhKBhQ.mjs').then(n => n._);
export { page };

View File

@ -0,0 +1,6 @@
export { renderers } from '../renderers.mjs';
export { onRequest } from '../_empty-middleware.mjs';
const page = () => import('./pages/_slug__TUDhKBhQ.mjs').then(n => n.d);
export { page };

View File

@ -0,0 +1,342 @@
import { isRemotePath, joinPaths } from '@astrojs/internal-helpers/path';
import { A as AstroError, E as ExpectedImage, L as LocalImageUsedWrongly, M as MissingImageDimension, U as UnsupportedImageFormat, I as IncompatibleDescriptorOptions, a as UnsupportedImageConversion, b as MissingSharp } from '../astro_5WdVqH1c.mjs';
const VALID_SUPPORTED_FORMATS = [
"jpeg",
"jpg",
"png",
"tiff",
"webp",
"gif",
"svg",
"avif"
];
const DEFAULT_OUTPUT_FORMAT = "webp";
const DEFAULT_HASH_PROPS = ["src", "width", "height", "format", "quality"];
function isESMImportedImage(src) {
return typeof src === "object";
}
function isRemoteImage(src) {
return typeof src === "string";
}
function matchPattern(url, remotePattern) {
return matchProtocol(url, remotePattern.protocol) && matchHostname(url, remotePattern.hostname, true) && matchPort(url, remotePattern.port) && matchPathname(url, remotePattern.pathname, true);
}
function matchPort(url, port) {
return !port || port === url.port;
}
function matchProtocol(url, protocol) {
return !protocol || protocol === url.protocol.slice(0, -1);
}
function matchHostname(url, hostname, allowWildcard) {
if (!hostname) {
return true;
} else if (!allowWildcard || !hostname.startsWith("*")) {
return hostname === url.hostname;
} else if (hostname.startsWith("**.")) {
const slicedHostname = hostname.slice(2);
return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname);
} else if (hostname.startsWith("*.")) {
const slicedHostname = hostname.slice(1);
const additionalSubdomains = url.hostname.replace(slicedHostname, "").split(".").filter(Boolean);
return additionalSubdomains.length === 1;
}
return false;
}
function matchPathname(url, pathname, allowWildcard) {
if (!pathname) {
return true;
} else if (!allowWildcard || !pathname.endsWith("*")) {
return pathname === url.pathname;
} else if (pathname.endsWith("/**")) {
const slicedPathname = pathname.slice(0, -2);
return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname);
} else if (pathname.endsWith("/*")) {
const slicedPathname = pathname.slice(0, -1);
const additionalPathChunks = url.pathname.replace(slicedPathname, "").split("/").filter(Boolean);
return additionalPathChunks.length === 1;
}
return false;
}
function isRemoteAllowed(src, {
domains = [],
remotePatterns = []
}) {
if (!isRemotePath(src))
return false;
const url = new URL(src);
return domains.some((domain) => matchHostname(url, domain)) || remotePatterns.some((remotePattern) => matchPattern(url, remotePattern));
}
function isLocalService(service) {
if (!service) {
return false;
}
return "transform" in service;
}
function parseQuality(quality) {
let result = parseInt(quality);
if (Number.isNaN(result)) {
return quality;
}
return result;
}
const baseService = {
propertiesToHash: DEFAULT_HASH_PROPS,
validateOptions(options) {
if (!options.src || typeof options.src !== "string" && typeof options.src !== "object") {
throw new AstroError({
...ExpectedImage,
message: ExpectedImage.message(
JSON.stringify(options.src),
typeof options.src,
JSON.stringify(options, (_, v) => v === void 0 ? null : v)
)
});
}
if (!isESMImportedImage(options.src)) {
if (options.src.startsWith("/@fs/") || !isRemotePath(options.src) && !options.src.startsWith("/")) {
throw new AstroError({
...LocalImageUsedWrongly,
message: LocalImageUsedWrongly.message(options.src)
});
}
let missingDimension;
if (!options.width && !options.height) {
missingDimension = "both";
} else if (!options.width && options.height) {
missingDimension = "width";
} else if (options.width && !options.height) {
missingDimension = "height";
}
if (missingDimension) {
throw new AstroError({
...MissingImageDimension,
message: MissingImageDimension.message(missingDimension, options.src)
});
}
} else {
if (!VALID_SUPPORTED_FORMATS.includes(options.src.format)) {
throw new AstroError({
...UnsupportedImageFormat,
message: UnsupportedImageFormat.message(
options.src.format,
options.src.src,
VALID_SUPPORTED_FORMATS
)
});
}
if (options.widths && options.densities) {
throw new AstroError(IncompatibleDescriptorOptions);
}
if (options.src.format === "svg") {
options.format = "svg";
}
if (options.src.format === "svg" && options.format !== "svg" || options.src.format !== "svg" && options.format === "svg") {
throw new AstroError(UnsupportedImageConversion);
}
}
if (!options.format) {
options.format = DEFAULT_OUTPUT_FORMAT;
}
if (options.width)
options.width = Math.round(options.width);
if (options.height)
options.height = Math.round(options.height);
return options;
},
getHTMLAttributes(options) {
const { targetWidth, targetHeight } = getTargetDimensions(options);
const { src, width, height, format, quality, densities, widths, formats, ...attributes } = options;
return {
...attributes,
width: targetWidth,
height: targetHeight,
loading: attributes.loading ?? "lazy",
decoding: attributes.decoding ?? "async"
};
},
getSrcSet(options) {
const srcSet = [];
const { targetWidth } = getTargetDimensions(options);
const { widths, densities } = options;
const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT;
let imageWidth = options.width;
let maxWidth = Infinity;
if (isESMImportedImage(options.src)) {
imageWidth = options.src.width;
maxWidth = imageWidth;
}
const {
width: transformWidth,
height: transformHeight,
...transformWithoutDimensions
} = options;
const allWidths = [];
if (densities) {
const densityValues = densities.map((density) => {
if (typeof density === "number") {
return density;
} else {
return parseFloat(density);
}
});
const densityWidths = densityValues.sort().map((density) => Math.round(targetWidth * density));
allWidths.push(
...densityWidths.map((width, index) => ({
maxTargetWidth: Math.min(width, maxWidth),
descriptor: `${densityValues[index]}x`
}))
);
} else if (widths) {
allWidths.push(
...widths.map((width) => ({
maxTargetWidth: Math.min(width, maxWidth),
descriptor: `${width}w`
}))
);
}
for (const { maxTargetWidth, descriptor } of allWidths) {
const srcSetTransform = { ...transformWithoutDimensions };
if (maxTargetWidth !== imageWidth) {
srcSetTransform.width = maxTargetWidth;
} else {
if (options.width && options.height) {
srcSetTransform.width = options.width;
srcSetTransform.height = options.height;
}
}
srcSet.push({
transform: srcSetTransform,
descriptor,
attributes: {
type: `image/${targetFormat}`
}
});
}
return srcSet;
},
getURL(options, imageConfig) {
const searchParams = new URLSearchParams();
if (isESMImportedImage(options.src)) {
searchParams.append("href", options.src.src);
} else if (isRemoteAllowed(options.src, imageConfig)) {
searchParams.append("href", options.src);
} else {
return options.src;
}
const params = {
w: "width",
h: "height",
q: "quality",
f: "format"
};
Object.entries(params).forEach(([param, key]) => {
options[key] && searchParams.append(param, options[key].toString());
});
const imageEndpoint = joinPaths("/", "/_image");
return `${imageEndpoint}?${searchParams}`;
},
parseURL(url) {
const params = url.searchParams;
if (!params.has("href")) {
return void 0;
}
const transform = {
src: params.get("href"),
width: params.has("w") ? parseInt(params.get("w")) : void 0,
height: params.has("h") ? parseInt(params.get("h")) : void 0,
format: params.get("f"),
quality: params.get("q")
};
return transform;
}
};
function getTargetDimensions(options) {
let targetWidth = options.width;
let targetHeight = options.height;
if (isESMImportedImage(options.src)) {
const aspectRatio = options.src.width / options.src.height;
if (targetHeight && !targetWidth) {
targetWidth = Math.round(targetHeight * aspectRatio);
} else if (targetWidth && !targetHeight) {
targetHeight = Math.round(targetWidth / aspectRatio);
} else if (!targetWidth && !targetHeight) {
targetWidth = options.src.width;
targetHeight = options.src.height;
}
}
return {
targetWidth,
targetHeight
};
}
let sharp;
const qualityTable = {
low: 25,
mid: 50,
high: 80,
max: 100
};
async function loadSharp() {
let sharpImport;
try {
sharpImport = (await import('sharp')).default;
} catch (e) {
throw new AstroError(MissingSharp);
}
return sharpImport;
}
const sharpService = {
validateOptions: baseService.validateOptions,
getURL: baseService.getURL,
parseURL: baseService.parseURL,
getHTMLAttributes: baseService.getHTMLAttributes,
getSrcSet: baseService.getSrcSet,
async transform(inputBuffer, transformOptions, config) {
if (!sharp)
sharp = await loadSharp();
const transform = transformOptions;
if (transform.format === "svg")
return { data: inputBuffer, format: "svg" };
const result = sharp(inputBuffer, {
failOnError: false,
pages: -1,
limitInputPixels: config.service.config.limitInputPixels
});
result.rotate();
if (transform.height && !transform.width) {
result.resize({ height: Math.round(transform.height) });
} else if (transform.width) {
result.resize({ width: Math.round(transform.width) });
}
if (transform.format) {
let quality = void 0;
if (transform.quality) {
const parsedQuality = parseQuality(transform.quality);
if (typeof parsedQuality === "number") {
quality = parsedQuality;
} else {
quality = transform.quality in qualityTable ? qualityTable[transform.quality] : void 0;
}
}
result.toFormat(transform.format, { quality });
}
const { data, info } = await result.toBuffer({ resolveWithObject: true });
return {
data,
format: info.format
};
}
};
var sharp_default = sharpService;
const sharp$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: sharp_default
}, Symbol.toStringTag, { value: 'Module' }));
export { DEFAULT_HASH_PROPS as D, isLocalService as a, isRemoteImage as b, isRemoteAllowed as c, isESMImportedImage as i, sharp$1 as s };

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
export { renderers } from '../renderers.mjs';
export { onRequest } from '../_empty-middleware.mjs';
const page = () => import('./pages/index_l5vwnKzb.mjs').then(n => n.b);
export { page };

View File

@ -0,0 +1,6 @@
export { renderers } from '../renderers.mjs';
export { onRequest } from '../_empty-middleware.mjs';
const page = () => import('./pages/index_l5vwnKzb.mjs').then(n => n.i);
export { page };

View File

@ -0,0 +1,6 @@
export { renderers } from '../renderers.mjs';
export { onRequest } from '../_empty-middleware.mjs';
const page = () => import('./pages/index_l5vwnKzb.mjs').then(n => n.a);
export { page };

View File

@ -0,0 +1,6 @@
export { renderers } from '../renderers.mjs';
export { onRequest } from '../_empty-middleware.mjs';
const page = () => import('./pages/node_hIg2I-Kh.mjs');
export { page };

View File

@ -0,0 +1,245 @@
/* empty css */
import 'html-escaper';
import { c as createAstro, d as createComponent, r as renderTemplate, m as maybeRenderHead, e as addAttribute, f as renderSlot, g as renderTransition, h as renderComponent, i as renderHead } from '../astro_5WdVqH1c.mjs';
import 'kleur/colors';
import 'clsx';
import { DocumentRenderer } from '@keystone-6/document-renderer';
/* empty css */
/* empty css */
const $$Astro$7 = createAstro("https://smartsheep.studio");
const $$Navbar = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$7, $$props, $$slots);
Astro2.self = $$Navbar;
const items = [
{
label: "\u60C5\u62A5",
children: [
{ href: "/posts", label: "\u8BB0\u5F55" },
{ href: "/events", label: "\u6D3B\u52A8" }
]
}
];
return renderTemplate`${maybeRenderHead()}<div class="fixed top-0 navbar shadow-md bg-base-100 lg:px-5 z-10"> <div class="navbar-start"> <div class="dropdown"> <div tabindex="0" role="button" class="btn btn-ghost lg:hidden"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path> </svg> </div> <ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"> ${items.map((item) => renderTemplate`<li> <a${addAttribute(item.href, "href")}>${item.label}</a> ${item.children && renderTemplate`<ul class="p-2"> ${item.children?.map((child) => renderTemplate`<li> <a${addAttribute(child.href, "href")}>${child.label}</a> </li>`)} </ul>`} </li>`)} </ul> </div> <a class="btn btn-ghost text-xl" href="/">山羊寒舍</a> </div> <div class="navbar-center hidden lg:flex"> <ul class="menu menu-horizontal px-1"> ${items.map((item) => renderTemplate`<li> ${item.children ? renderTemplate`<details> <summary>${item.label}</summary> <ul class="p-2"> ${item.children?.map((child) => renderTemplate`<li> <a${addAttribute(child.href, "href")}>${child.label}</a> </li>`)} </ul> </details>` : renderTemplate`<a${addAttribute(item.href, "href")}>${item.label}</a>`} </li>`)} </ul> </div> <div class="navbar-end"> <label class="swap swap-rotate px-[16px]"> <input type="checkbox" class="theme-controller" value="light" checked> <svg class="swap-on fill-current w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"></path> </svg> <svg class="swap-off fill-current w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"></path> </svg> </label> </div> </div>`;
}, "/Users/littlesheep/Documents/Projects/Capital/src/components/Navbar.astro", void 0);
const $$Astro$6 = createAstro("https://smartsheep.studio");
const $$ViewTransitions = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$6, $$props, $$slots);
Astro2.self = $$ViewTransitions;
const { fallback = "animate" } = Astro2.props;
return renderTemplate`<meta name="astro-view-transitions-enabled" content="true"><meta name="astro-view-transitions-fallback"${addAttribute(fallback, "content")}>`;
}, "/Users/littlesheep/Documents/Projects/Capital/node_modules/astro/components/ViewTransitions.astro", void 0);
var __freeze = Object.freeze;
var __defProp = Object.defineProperty;
var __template = (cooked, raw) => __freeze(__defProp(cooked, "raw", { value: __freeze(raw || cooked.slice()) }));
var _a;
const $$Astro$5 = createAstro("https://smartsheep.studio");
const $$RootLayout = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$5, $$props, $$slots);
Astro2.self = $$RootLayout;
const { title } = Astro2.props;
return renderTemplate(_a || (_a = __template(['<html lang="en" data-astro-cid-mdysn4oi> <head><meta charset="utf-8"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="viewport" content="width=device-width"><meta name="generator"', ">", "", "", "", "</head> <body data-astro-cid-mdysn4oi> <!-- Header --> ", " <!-- Content --> <main data-astro-cid-mdysn4oi", "> ", ' </main> <!-- Styles --> <script async src="https://analytics.smartsheep.studio/script.js" data-website-id="9d676a27-b473-44a3-b444-5a7d851e31e8"><\/script> </body> </html>'])), addAttribute(Astro2.generator, "content"), title && renderTemplate`<title>山羊寒舍 | ${title}</title>`, !title && renderTemplate`<title>山羊寒舍</title>`, renderComponent($$result, "ViewTransitions", $$ViewTransitions, { "data-astro-cid-mdysn4oi": true }), renderHead(), renderComponent($$result, "Navbar", $$Navbar, { "data-astro-cid-mdysn4oi": true }), addAttribute(renderTransition($$result, "53mar5bf", "slide", ""), "data-astro-transition-scope"), renderSlot($$result, $$slots["default"]));
}, "/Users/littlesheep/Documents/Projects/Capital/src/layouts/RootLayout.astro", "self");
const $$Astro$4 = createAstro("https://smartsheep.studio");
const $$PageLayout = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$4, $$props, $$slots);
Astro2.self = $$PageLayout;
const { title } = Astro2.props;
return renderTemplate`${renderComponent($$result, "RootLayout", $$RootLayout, { "title": title }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="container mx-auto h-fullpage mt-header"> ${renderSlot($$result2, $$slots["default"])} </main> ` })}`;
}, "/Users/littlesheep/Documents/Projects/Capital/src/layouts/PageLayout.astro", void 0);
const defaultCms = "https://smartsheep.studio";
async function graphQuery(query, variables) {
const response = await fetch(`${process.env.PUBLIC_CMS ?? defaultCms}/api/graphql`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query,
variables
})
});
return await response.json();
}
const POST_TYPES = {
article: "文章",
podcast: "播客",
announcements: "通告"
};
const $$Astro$3 = createAstro("https://smartsheep.studio");
const $$PostList = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$3, $$props, $$slots);
Astro2.self = $$PostList;
const { posts } = Astro2.props;
return renderTemplate`${maybeRenderHead()}<div class="grid justify-items-strench shadow-lg"> ${posts?.map((item) => renderTemplate`<a${addAttribute(`/p/${item.slug}`, "href")}> <div class="card sm:card-side hover:bg-base-200 transition-colors sm:max-w-none"> ${item.cover.image.url && renderTemplate`<figure class="mx-auto w-full object-cover p-6 max-sm:pb-0 sm:max-w-[12rem] sm:pe-0"> <img loading="lazy"${addAttribute(item.cover.image.url, "src")} class="border-base-content bg-base-300 rounded-btn border border-opacity-5"${addAttribute(item.title, "alt")}> </figure>`} <div class="card-body"> <h2 class="text-xl">${item.title}</h2> <div class="mx-[-2px] mt-[-4px]"> <span class="badge badge-accent">${POST_TYPES[item.type]}</span> ${item.categories?.map((category) => renderTemplate`<span class="badge badge-primary">${category.name}</span>`)} ${item.tags?.map((tag) => renderTemplate`<span class="badge badge-secondary">${tag.name}</span>`)} </div> <div class="text-xs opacity-60 line-clamp-3"> ${item.description} </div> </div> </div> </a>`)} </div>`;
}, "/Users/littlesheep/Documents/Projects/Capital/src/components/PostList.astro", void 0);
const $$Astro$2 = createAstro("https://smartsheep.studio");
const prerender$2 = false;
const $$slug$2 = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$2, $$props, $$slots);
Astro2.self = $$slug$2;
const { slug } = Astro2.params;
const { posts } = (await graphQuery(
`query Query($where: PostWhereInput!, $orderBy: [PostOrderByInput!]!) {
posts(where: $where, orderBy: $orderBy) {
slug
type
title
description
cover {
image {
url
}
}
content {
document
}
categories {
name
}
tags {
name
}
createdAt
}
}`,
{
orderBy: [
{
createdAt: "desc"
}
],
where: { categories: { some: { slug: { equals: slug } } } }
}
)).data;
return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u5206\u7C7B\u68C0\u7D22" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="pt-16 pb-6 px-6"> <h1 class="text-4xl font-bold">分类检索</h1> <p class="pt-3"></p> </div> ${renderComponent($$result2, "PostList", $$PostList, { "posts": posts })} </div> ` })}`;
}, "/Users/littlesheep/Documents/Projects/Capital/src/pages/categories/[slug].astro", void 0);
const $$file$2 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/categories/[slug].astro";
const $$url$2 = "/categories/[slug]";
const _slug_$2 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$slug$2,
file: $$file$2,
prerender: prerender$2,
url: $$url$2
}, Symbol.toStringTag, { value: 'Module' }));
const $$Astro$1 = createAstro("https://smartsheep.studio");
const prerender$1 = false;
const $$slug$1 = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots);
Astro2.self = $$slug$1;
const { slug } = Astro2.params;
const { post } = (await graphQuery(
`query Query($where: PostWhereUniqueInput!) {
post(where: $where) {
slug
type
title
description
author {
name
}
assets {
caption
url
type
}
cover {
image {
url
}
}
content {
document
}
categories {
slug
name
}
tags {
slug
name
}
createdAt
}
}`,
{
where: { slug }
}
)).data;
return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": post.title, "data-astro-cid-gysqo7gh": true }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="wrapper" data-astro-cid-gysqo7gh> <div class="card w-full shadow-xl" data-astro-cid-gysqo7gh> ${post.cover && renderTemplate`<figure data-astro-cid-gysqo7gh> <img${addAttribute(post.cover.image.url, "src")}${addAttribute(post.title, "alt")} data-astro-cid-gysqo7gh> </figure>`} <div class="card-body" data-astro-cid-gysqo7gh> <h2 class="card-title" data-astro-cid-gysqo7gh>${post.title}</h2> <p class="description" data-astro-cid-gysqo7gh>${post.description ?? "No description"}</p> <div class="divider" data-astro-cid-gysqo7gh></div> ${post.assets?.length > 0 && renderTemplate`<div class="mb-5 w-full" data-astro-cid-gysqo7gh> ${renderComponent($$result2, "Media", null, { "client:only": true, "sources": post.assets, "author": post.author, "client:component-hydration": "only", "data-astro-cid-gysqo7gh": true, "client:component-path": "/Users/littlesheep/Documents/Projects/Capital/src/components/posts/Media", "client:component-export": "default" })} </div>`} <div class="prose max-w-none" data-astro-cid-gysqo7gh> ${renderComponent($$result2, "DocumentRenderer", DocumentRenderer, { "document": post.content.document, "data-astro-cid-gysqo7gh": true })} </div> </div> </div> <div class="h-fit sticky top-header" data-astro-cid-gysqo7gh> <div class="card shadow-xl" data-astro-cid-gysqo7gh> <div class="card-body" data-astro-cid-gysqo7gh> <div class="gap-2 text-sm metadata description" data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh></div> <div data-astro-cid-gysqo7gh>${post.author?.name ?? "\u4F5A\u540D"}</div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh></div> <div class="text-accent" data-astro-cid-gysqo7gh> ${POST_TYPES[post.type]} </div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh></div> <div class="flex gap-1" data-astro-cid-gysqo7gh> ${post.categories?.map((category) => renderTemplate`<a${addAttribute(`/categories/${category.slug}`, "href")} class="link link-primary" data-astro-cid-gysqo7gh> ${category.name} </a>`)} </div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh></div> <div class="flex gap-1" data-astro-cid-gysqo7gh> ${post.tags?.map((tag) => renderTemplate`<a${addAttribute(`/tags/${tag.slug}`, "href")} class="link link-secondary" data-astro-cid-gysqo7gh> ${tag.name} </a>`)} </div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh></div> <div data-astro-cid-gysqo7gh>${new Date(post.createdAt).toLocaleString()}</div> </div> </div> </div> </div> </div> </div> ` })} `;
}, "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/[slug].astro", void 0);
const $$file$1 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/[slug].astro";
const $$url$1 = "/posts/[slug]";
const _slug_$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$slug$1,
file: $$file$1,
prerender: prerender$1,
url: $$url$1
}, Symbol.toStringTag, { value: 'Module' }));
const $$Astro = createAstro("https://smartsheep.studio");
const prerender = false;
const $$slug = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$slug;
const { slug } = Astro2.params;
const { posts } = (await graphQuery(
`query Query($where: PostWhereInput!, $orderBy: [PostOrderByInput!]!) {
posts(where: $where, orderBy: $orderBy) {
slug
type
title
description
cover {
image {
url
}
}
content {
document
}
categories {
name
}
tags {
name
}
createdAt
}
}`,
{
orderBy: [
{
createdAt: "desc"
}
],
where: { tags: { some: { slug: { equals: slug } } } }
}
)).data;
return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u6807\u7B7E\u68C0\u7D22" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="pt-16 pb-6 px-6"> <h1 class="text-4xl font-bold">标签检索</h1> <p class="pt-3"></p> </div> ${renderComponent($$result2, "PostList", $$PostList, { "posts": posts })} </div> ` })}`;
}, "/Users/littlesheep/Documents/Projects/Capital/src/pages/tags/[slug].astro", void 0);
const $$file = "/Users/littlesheep/Documents/Projects/Capital/src/pages/tags/[slug].astro";
const $$url = "/tags/[slug]";
const _slug_ = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$slug,
file: $$file,
prerender,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
export { $$PageLayout as $, _slug_$2 as _, $$PostList as a, $$RootLayout as b, _slug_$1 as c, _slug_ as d, graphQuery as g };

View File

@ -0,0 +1,153 @@
/* empty css */
import { c as createAstro, d as createComponent, r as renderTemplate, h as renderComponent, m as maybeRenderHead, e as addAttribute } from '../astro_5WdVqH1c.mjs';
import 'kleur/colors';
import 'html-escaper';
import { g as graphQuery, $ as $$PageLayout, a as $$PostList, b as $$RootLayout } from './_slug__TUDhKBhQ.mjs';
import { DocumentRenderer } from '@keystone-6/document-renderer';
import 'clsx';
/* empty css */
const $$Astro$2 = createAstro("https://smartsheep.studio");
const prerender$2 = false;
const $$Index$2 = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$2, $$props, $$slots);
Astro2.self = $$Index$2;
const { events } = (await graphQuery(
`query Query($where: EventWhereInput!) {
events(where: $where) {
slug
title
description
content {
document
}
createdAt
}
}`,
{
where: {
isHistory: {
equals: true
}
}
}
)).data;
return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u6D3B\u52A8" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="card w-full shadow-xl"> <div class="card-body"> <h2 class="card-title">活动</h2> <p></p> <div class="divider"></div> <ul class="timeline timeline-snap-icon max-md:timeline-compact timeline-vertical"> ${events?.map((item, idx) => {
let align = idx % 2 === 0 ? "timeline-start" : "timeline-end";
let textAlign = idx % 2 === 0 ? "md:text-right" : "md:text-left";
return renderTemplate`<li> ${idx > 0 && renderTemplate`<hr>`} <div class="timeline-middle"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"></path> </svg> </div> <div${addAttribute(`${align} ${textAlign} mb-10`, "class")}> <time class="font-mono italic"> ${new Date(item.createdAt).toLocaleDateString()} </time> <div class="text-lg font-black">${item.title}</div> ${renderComponent($$result2, "DocumentRenderer", DocumentRenderer, { "document": item.content.document })} </div> <hr> </li>`;
})} </ul> <div class="text-center max-md:text-left italic">
我们的故事还在继续
</div> </div> </div> </div> ` })}`;
}, "/Users/littlesheep/Documents/Projects/Capital/src/pages/events/index.astro", void 0);
const $$file$2 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/events/index.astro";
const $$url$2 = "/events";
const index$2 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$Index$2,
file: $$file$2,
prerender: prerender$2,
url: $$url$2
}, Symbol.toStringTag, { value: 'Module' }));
const $$Astro$1 = createAstro("https://smartsheep.studio");
const prerender$1 = false;
const $$Index$1 = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots);
Astro2.self = $$Index$1;
const { posts } = (await graphQuery(
`query Query($where: PostWhereInput!, $orderBy: [PostOrderByInput!]!) {
posts(where: $where, orderBy: $orderBy) {
slug
type
title
description
cover {
image {
url
}
}
content {
document
}
categories {
name
}
tags {
name
}
createdAt
}
}`,
{
orderBy: [
{
createdAt: "desc"
}
],
where: {}
}
)).data;
return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u8BB0\u5F55" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="pt-16 pb-6 px-6"> <h1 class="text-4xl font-bold">记录</h1> <p class="pt-2"></p> </div> ${renderComponent($$result2, "PostList", $$PostList, { "posts": posts })} </div> ` })}`;
}, "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/index.astro", void 0);
const $$file$1 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/index.astro";
const $$url$1 = "/posts";
const index$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$Index$1,
file: $$file$1,
prerender: prerender$1,
url: $$url$1
}, Symbol.toStringTag, { value: 'Module' }));
const $$Astro = createAstro("https://smartsheep.studio");
const prerender = false;
const $$Index = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$Index;
const { events } = (await graphQuery(
`query Query($where: EventWhereInput!) {
events(where: $where) {
slug
title
description
createdAt
}
}`,
{
where: {
isHistory: {
equals: true
}
}
}
)).data;
return renderTemplate`${renderComponent($$result, "RootLayout", $$RootLayout, { "data-astro-cid-j7pv25f6": true }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-h-fullpage mt-header wrapper px-5 snap-y snap-mandatory" data-astro-cid-j7pv25f6> <div id="hello" class="hero h-fullpage snap-start" data-astro-cid-j7pv25f6> <div class="hero-content w-full grid grid-cols-1 md:grid-cols-2 max-md:gap-[60px]" data-astro-cid-j7pv25f6> <div class="max-md:text-center" data-astro-cid-j7pv25f6> <h1 class="text-5xl font-bold" data-astro-cid-j7pv25f6>你好呀 👋</h1> <p class="py-6" data-astro-cid-j7pv25f6>
欢迎来到 SmartSheep Studio
的官方网站在这里了解订阅跟踪我们的最新消息
接触我们最大的官方社区并且尝试最新产品参与各种活动提供反馈让我们更好的服务您
</p> <a href="#about" class="btn btn-primary btn-md" data-astro-cid-j7pv25f6></a> </div> <div class="flex justify-center md:justify-end max-md:order-first" data-astro-cid-j7pv25f6> <div class="spinning p-3 md:p-5 shadow-2xl aspect-square rounded-[30%] w-[192px] md:w-[256px] lg:w-[384px]" data-astro-cid-j7pv25f6> <img src="/favicon.svg" alt="logo" loading="lazy" data-astro-cid-j7pv25f6> </div> </div> </div> </div> <div id="about" class="hero h-fullpage snap-start" data-astro-cid-j7pv25f6> <div class="hero-content w-full grid grid-cols-1 md:grid-cols-2 max-md:gap-[60px]" data-astro-cid-j7pv25f6> <div class="flex justify-center md:justify-start" data-astro-cid-j7pv25f6> <div class="stats shadow overflow-x-auto" data-astro-cid-j7pv25f6> <div class="stat" data-astro-cid-j7pv25f6> <div class="stat-figure text-secondary" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-8 h-8 stroke-current" data-astro-cid-j7pv25f6><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" data-astro-cid-j7pv25f6></path></svg> </div> <div class="stat-title" data-astro-cid-j7pv25f6>People</div> <div class="stat-value" data-astro-cid-j7pv25f6>1</div> <div class="stat-desc" data-astro-cid-j7pv25f6>2019 - ${(/* @__PURE__ */ new Date()).getFullYear()}</div> </div> <div class="stat" data-astro-cid-j7pv25f6> <div class="stat-figure text-secondary" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-8 h-8 stroke-current" data-astro-cid-j7pv25f6><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" data-astro-cid-j7pv25f6></path></svg> </div> <div class="stat-title" data-astro-cid-j7pv25f6>Clients</div> <div class="stat-value" data-astro-cid-j7pv25f6>180</div> <div class="stat-desc" data-astro-cid-j7pv25f6> 80 (44%)</div> </div> <div class="stat" data-astro-cid-j7pv25f6> <div class="stat-figure text-secondary" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-8 h-8 stroke-current" data-astro-cid-j7pv25f6><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" data-astro-cid-j7pv25f6></path></svg> </div> <div class="stat-title" data-astro-cid-j7pv25f6>Products</div> <div class="stat-value" data-astro-cid-j7pv25f6>4</div> <div class="stat-desc" data-astro-cid-j7pv25f6> 8 (67%)</div> </div> </div> </div> <div class="max-md:text-center" data-astro-cid-j7pv25f6> <h1 class="text-5xl font-bold" data-astro-cid-j7pv25f6> 🔖</h1> <p class="py-6" data-astro-cid-j7pv25f6>
我们是一群充满活力对开源充满热情的开发者成立于 2019
自那年起我们一直在开发让人喜欢的开源软件在我们这里取之于开源用之于开源
不仅是原则更是我们信仰的座右铭
</p> <a href="#history" class="btn btn-primary btn-md pl-[24px]" data-astro-cid-j7pv25f6>
查看岁月史书
</a> </div> </div> </div> <div id="history" class="flex flex-col justify-center items-center h-fullpage snap-start" data-astro-cid-j7pv25f6> <div class="text-center" data-astro-cid-j7pv25f6> <div data-astro-cid-j7pv25f6> <h1 class="text-4xl font-bold" data-astro-cid-j7pv25f6></h1> <p class="pt-2 pb-4 tracking-[8px]" data-astro-cid-j7pv25f6></p> <ul class="pb-6 mx-[-20px] max-w-[100vw] px-5 flex justify-center history timeline timeline-horizontal" data-astro-cid-j7pv25f6> ${events?.map((item, idx) => renderTemplate`<li data-astro-cid-j7pv25f6> ${idx > 0 && renderTemplate`<hr data-astro-cid-j7pv25f6>`} <div class="timeline-start" data-astro-cid-j7pv25f6> ${new Date(item.createdAt).toLocaleDateString()} </div> <div class="timeline-middle" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" data-astro-cid-j7pv25f6> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" data-astro-cid-j7pv25f6></path> </svg> </div> <div class="timeline-end timeline-box" data-astro-cid-j7pv25f6> <h2 class="font-bold text-lg" data-astro-cid-j7pv25f6>${item.title}</h2> <div class="line-clamp-2" data-astro-cid-j7pv25f6>${item.description}</div> </div> ${idx < events?.length - 1 && renderTemplate`<hr data-astro-cid-j7pv25f6>`} </li>`)} </ul> <a class="btn btn-primary" href="/events" data-astro-cid-j7pv25f6></a> </div> </div> </div> </div> ` })} `;
}, "/Users/littlesheep/Documents/Projects/Capital/src/pages/index.astro", void 0);
const $$file = "/Users/littlesheep/Documents/Projects/Capital/src/pages/index.astro";
const $$url = "";
const index = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$Index,
file: $$file,
prerender,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
export { index$1 as a, index as b, index$2 as i };

View File

@ -0,0 +1,240 @@
import { isRemotePath } from '@astrojs/internal-helpers/path';
import { readFile } from 'fs/promises';
import mime from 'mime/lite.js';
import 'os';
import { A as AstroError, j as InvalidImageService, k as ExpectedImageOptions, E as ExpectedImage, c as createAstro, d as createComponent, l as ImageMissingAlt, r as renderTemplate, m as maybeRenderHead, e as addAttribute, s as spreadAttributes } from '../astro_5WdVqH1c.mjs';
import { i as isESMImportedImage, a as isLocalService, b as isRemoteImage, D as DEFAULT_HASH_PROPS, c as isRemoteAllowed } from '../astro/assets-service_4dMyVCFm.mjs';
import 'html-escaper';
import 'clsx';
async function getConfiguredImageService() {
if (!globalThis?.astroAsset?.imageService) {
const { default: service } = await import(
// @ts-expect-error
'../astro/assets-service_4dMyVCFm.mjs'
).then(n => n.s).catch((e) => {
const error = new AstroError(InvalidImageService);
error.cause = e;
throw error;
});
if (!globalThis.astroAsset)
globalThis.astroAsset = {};
globalThis.astroAsset.imageService = service;
return service;
}
return globalThis.astroAsset.imageService;
}
async function getImage$1(options, imageConfig) {
if (!options || typeof options !== "object") {
throw new AstroError({
...ExpectedImageOptions,
message: ExpectedImageOptions.message(JSON.stringify(options))
});
}
if (typeof options.src === "undefined") {
throw new AstroError({
...ExpectedImage,
message: ExpectedImage.message(
options.src,
"undefined",
JSON.stringify(options)
)
});
}
const service = await getConfiguredImageService();
const resolvedOptions = {
...options,
src: typeof options.src === "object" && "then" in options.src ? (await options.src).default ?? await options.src : options.src
};
const originalPath = isESMImportedImage(resolvedOptions.src) ? resolvedOptions.src.fsPath : resolvedOptions.src;
const clonedSrc = isESMImportedImage(resolvedOptions.src) ? (
// @ts-expect-error - clone is a private, hidden prop
resolvedOptions.src.clone ?? resolvedOptions.src
) : resolvedOptions.src;
resolvedOptions.src = clonedSrc;
const validatedOptions = service.validateOptions ? await service.validateOptions(resolvedOptions, imageConfig) : resolvedOptions;
const srcSetTransforms = service.getSrcSet ? await service.getSrcSet(validatedOptions, imageConfig) : [];
let imageURL = await service.getURL(validatedOptions, imageConfig);
let srcSets = await Promise.all(
srcSetTransforms.map(async (srcSet) => ({
transform: srcSet.transform,
url: await service.getURL(srcSet.transform, imageConfig),
descriptor: srcSet.descriptor,
attributes: srcSet.attributes
}))
);
if (isLocalService(service) && globalThis.astroAsset.addStaticImage && !(isRemoteImage(validatedOptions.src) && imageURL === validatedOptions.src)) {
const propsToHash = service.propertiesToHash ?? DEFAULT_HASH_PROPS;
imageURL = globalThis.astroAsset.addStaticImage(validatedOptions, propsToHash, originalPath);
srcSets = srcSetTransforms.map((srcSet) => ({
transform: srcSet.transform,
url: globalThis.astroAsset.addStaticImage(srcSet.transform, propsToHash, originalPath),
descriptor: srcSet.descriptor,
attributes: srcSet.attributes
}));
}
return {
rawOptions: resolvedOptions,
options: validatedOptions,
src: imageURL,
srcSet: {
values: srcSets,
attribute: srcSets.map((srcSet) => `${srcSet.url} ${srcSet.descriptor}`).join(", ")
},
attributes: service.getHTMLAttributes !== void 0 ? await service.getHTMLAttributes(validatedOptions, imageConfig) : {}
};
}
const fnv1a52 = (str) => {
const len = str.length;
let i = 0, t0 = 0, v0 = 8997, t1 = 0, v1 = 33826, t2 = 0, v2 = 40164, t3 = 0, v3 = 52210;
while (i < len) {
v0 ^= str.charCodeAt(i++);
t0 = v0 * 435;
t1 = v1 * 435;
t2 = v2 * 435;
t3 = v3 * 435;
t2 += v0 << 8;
t3 += v1 << 8;
t1 += t0 >>> 16;
v0 = t0 & 65535;
t2 += t1 >>> 16;
v1 = t1 & 65535;
v3 = t3 + (t2 >>> 16) & 65535;
v2 = t2 & 65535;
}
return (v3 & 15) * 281474976710656 + v2 * 4294967296 + v1 * 65536 + (v0 ^ v3 >> 4);
};
const etag = (payload, weak = false) => {
const prefix = weak ? 'W/"' : '"';
return prefix + fnv1a52(payload).toString(36) + payload.length.toString(36) + '"';
};
const $$Astro$1 = createAstro("https://smartsheep.studio");
const $$Image = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots);
Astro2.self = $$Image;
const props = Astro2.props;
if (props.alt === void 0 || props.alt === null) {
throw new AstroError(ImageMissingAlt);
}
if (typeof props.width === "string") {
props.width = parseInt(props.width);
}
if (typeof props.height === "string") {
props.height = parseInt(props.height);
}
const image = await getImage(props);
const additionalAttributes = {};
if (image.srcSet.values.length > 0) {
additionalAttributes.srcset = image.srcSet.attribute;
}
return renderTemplate`${maybeRenderHead()}<img${addAttribute(image.src, "src")}${spreadAttributes(additionalAttributes)}${spreadAttributes(image.attributes)}>`;
}, "/Users/littlesheep/Documents/Projects/Capital/node_modules/astro/components/Image.astro", void 0);
const $$Astro = createAstro("https://smartsheep.studio");
const $$Picture = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$Picture;
const defaultFormats = ["webp"];
const defaultFallbackFormat = "png";
const specialFormatsFallback = ["gif", "svg", "jpg", "jpeg"];
const { formats = defaultFormats, pictureAttributes = {}, fallbackFormat, ...props } = Astro2.props;
if (props.alt === void 0 || props.alt === null) {
throw new AstroError(ImageMissingAlt);
}
const optimizedImages = await Promise.all(
formats.map(
async (format) => await getImage({ ...props, format, widths: props.widths, densities: props.densities })
)
);
let resultFallbackFormat = fallbackFormat ?? defaultFallbackFormat;
if (!fallbackFormat && isESMImportedImage(props.src) && specialFormatsFallback.includes(props.src.format)) {
resultFallbackFormat = props.src.format;
}
const fallbackImage = await getImage({
...props,
format: resultFallbackFormat,
widths: props.widths,
densities: props.densities
});
const imgAdditionalAttributes = {};
const sourceAdditionaAttributes = {};
if (props.sizes) {
sourceAdditionaAttributes.sizes = props.sizes;
}
if (fallbackImage.srcSet.values.length > 0) {
imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute;
}
return renderTemplate`${maybeRenderHead()}<picture${spreadAttributes(pictureAttributes)}> ${Object.entries(optimizedImages).map(([_, image]) => {
const srcsetAttribute = props.densities || !props.densities && !props.widths ? `${image.src}${image.srcSet.values.length > 0 ? ", " + image.srcSet.attribute : ""}` : image.srcSet.attribute;
return renderTemplate`<source${addAttribute(srcsetAttribute, "srcset")}${addAttribute("image/" + image.options.format, "type")}${spreadAttributes(sourceAdditionaAttributes)}>`;
})} <img${addAttribute(fallbackImage.src, "src")}${spreadAttributes(imgAdditionalAttributes)}${spreadAttributes(fallbackImage.attributes)}> </picture>`;
}, "/Users/littlesheep/Documents/Projects/Capital/node_modules/astro/components/Picture.astro", void 0);
const imageConfig = {"service":{"entrypoint":"astro/assets/services/sharp","config":{}},"domains":[],"remotePatterns":[],"endpoint":"astro/assets/endpoint/node"};
const assetsDir = new URL("file:///Users/littlesheep/Documents/Projects/Capital/dist/client/");
const getImage = async (options) => await getImage$1(options, imageConfig);
async function loadLocalImage(src, url) {
const filePath = new URL("." + src, assetsDir);
let buffer = void 0;
try {
buffer = await readFile(filePath);
} catch (e) {
const sourceUrl = new URL(src, url.origin);
buffer = await loadRemoteImage(sourceUrl);
}
return buffer;
}
async function loadRemoteImage(src) {
try {
const res = await fetch(src);
if (!res.ok) {
return void 0;
}
return Buffer.from(await res.arrayBuffer());
} catch (err) {
return void 0;
}
}
const GET = async ({ request }) => {
try {
const imageService = await getConfiguredImageService();
if (!("transform" in imageService)) {
throw new Error("Configured image service is not a local service");
}
const url = new URL(request.url);
const transform = await imageService.parseURL(url, imageConfig);
if (!transform?.src) {
throw new Error("Incorrect transform returned by `parseURL`");
}
let inputBuffer = void 0;
if (isRemotePath(transform.src)) {
if (isRemoteAllowed(transform.src, imageConfig) === false) {
return new Response("Forbidden", { status: 403 });
}
inputBuffer = await loadRemoteImage(new URL(transform.src));
} else {
inputBuffer = await loadLocalImage(transform.src, url);
}
if (!inputBuffer) {
return new Response("Not Found", { status: 404 });
}
const { data, format } = await imageService.transform(inputBuffer, transform, imageConfig);
return new Response(data, {
status: 200,
headers: {
"Content-Type": mime.getType(format) ?? `image/${format}`,
"Cache-Control": "public, max-age=31536000",
ETag: etag(data.toString()),
Date: (/* @__PURE__ */ new Date()).toUTCString()
}
});
} catch (err) {
console.error("Could not process image request:", err);
return new Response(`Server Error: ${err}`, { status: 500 });
}
};
export { GET };

View File

@ -0,0 +1,31 @@
import { parse, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml';
import { createElement, Fragment } from 'react';
let ids = 0;
function convert(children) {
let doc = parse(children.toString().trim());
let id = ids++;
let key = 0;
function createReactElementFromNode(node) {
const childVnodes =
Array.isArray(node.children) && node.children.length
? node.children.map((child) => createReactElementFromNode(child)).filter(Boolean)
: undefined;
if (node.type === DOCUMENT_NODE) {
return createElement(Fragment, {}, childVnodes);
} else if (node.type === ELEMENT_NODE) {
const { class: className, ...props } = node.attributes;
return createElement(node.name, { ...props, className, key: `${id}-${key++}` }, childVnodes);
} else if (node.type === TEXT_NODE) {
// 0-length text gets omitted in JSX
return node.value.trim() ? node.value : undefined;
}
}
const root = createReactElementFromNode(doc);
return root.props.children;
}
export { convert as default };

2355
test/data/warden/dist/server/entry.mjs vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,263 @@
import React, { createElement } from 'react';
import ReactDOM from 'react-dom/server.rs';
/**
* Astro passes `children` as a string of HTML, so we need
* a wrapper `div` to render that content as VNodes.
*
* As a bonus, we can signal to React that this subtree is
* entirely static and will never change via `shouldComponentUpdate`.
*/
const StaticHtml = ({ value, name, hydrate = true }) => {
if (!value) return null;
const tagName = hydrate ? 'astro-slot' : 'astro-static-slot';
return createElement(tagName, {
name,
suppressHydrationWarning: true,
dangerouslySetInnerHTML: { __html: value },
});
};
/**
* This tells React to opt-out of re-rendering this subtree,
* In addition to being a performance optimization,
* this also allows other frameworks to attach to `children`.
*
* See https://preactjs.com/guide/v8/external-dom-mutations
*/
StaticHtml.shouldComponentUpdate = () => false;
const contexts = new WeakMap();
const ID_PREFIX = 'r';
function getContext(rendererContextResult) {
if (contexts.has(rendererContextResult)) {
return contexts.get(rendererContextResult);
}
const ctx = {
currentIndex: 0,
get id() {
return ID_PREFIX + this.currentIndex.toString();
},
};
contexts.set(rendererContextResult, ctx);
return ctx;
}
function incrementId(rendererContextResult) {
const ctx = getContext(rendererContextResult);
const id = ctx.id;
ctx.currentIndex++;
return id;
}
const opts = {
experimentalReactChildren: false
};
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
const reactTypeof = Symbol.for('react.element');
function errorIsComingFromPreactComponent(err) {
return (
err.message &&
(err.message.startsWith("Cannot read property '__H'") ||
err.message.includes("(reading '__H')"))
);
}
async function check(Component, props, children) {
// Note: there are packages that do some unholy things to create "components".
// Checking the $$typeof property catches most of these patterns.
if (typeof Component === 'object') {
return Component['$$typeof'].toString().slice('Symbol('.length).startsWith('react');
}
if (typeof Component !== 'function') return false;
if (Component.name === 'QwikComponent') return false;
// Preact forwarded-ref components can be functions, which React does not support
if (typeof Component === 'function' && Component['$$typeof'] === Symbol.for('react.forward_ref'))
return false;
if (Component.prototype != null && typeof Component.prototype.render === 'function') {
return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component);
}
let error = null;
let isReactComponent = false;
function Tester(...args) {
try {
const vnode = Component(...args);
if (vnode && vnode['$$typeof'] === reactTypeof) {
isReactComponent = true;
}
} catch (err) {
if (!errorIsComingFromPreactComponent(err)) {
error = err;
}
}
return React.createElement('div');
}
await renderToStaticMarkup(Tester, props, children, {});
if (error) {
throw error;
}
return isReactComponent;
}
async function getNodeWritable() {
let nodeStreamBuiltinModuleName = 'node:stream';
let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName);
return Writable;
}
function needsHydration(metadata) {
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
return metadata.astroStaticSlot ? !!metadata.hydrate : true;
}
async function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
let prefix;
if (this && this.result) {
prefix = incrementId(this.result);
}
const attrs = { prefix };
delete props['class'];
const slots = {};
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key);
slots[name] = React.createElement(StaticHtml, {
hydrate: needsHydration(metadata),
value,
name,
});
}
// Note: create newProps to avoid mutating `props` before they are serialized
const newProps = {
...props,
...slots,
};
const newChildren = children ?? props.children;
if (children && opts.experimentalReactChildren) {
attrs['data-react-children'] = true;
const convert = await import('./chunks/vnode-children_3wEZly-Z.mjs').then((mod) => mod.default);
newProps.children = convert(children);
} else if (newChildren != null) {
newProps.children = React.createElement(StaticHtml, {
hydrate: needsHydration(metadata),
value: newChildren,
});
}
const vnode = React.createElement(Component, newProps);
const renderOptions = {
identifierPrefix: prefix,
};
let html;
if (metadata?.hydrate) {
if ('renderToReadableStream' in ReactDOM) {
html = await renderToReadableStreamAsync(vnode, renderOptions);
} else {
html = await renderToPipeableStreamAsync(vnode, renderOptions);
}
} else {
if ('renderToReadableStream' in ReactDOM) {
html = await renderToReadableStreamAsync(vnode, renderOptions);
} else {
html = await renderToStaticNodeStreamAsync(vnode, renderOptions);
}
}
return { html, attrs };
}
async function renderToPipeableStreamAsync(vnode, options) {
const Writable = await getNodeWritable();
let html = '';
return new Promise((resolve, reject) => {
let error = undefined;
let stream = ReactDOM.renderToPipeableStream(vnode, {
...options,
onError(err) {
error = err;
reject(error);
},
onAllReady() {
stream.pipe(
new Writable({
write(chunk, _encoding, callback) {
html += chunk.toString('utf-8');
callback();
},
destroy() {
resolve(html);
},
})
);
},
});
});
}
async function renderToStaticNodeStreamAsync(vnode, options) {
const Writable = await getNodeWritable();
let html = '';
return new Promise((resolve, reject) => {
let stream = ReactDOM.renderToStaticNodeStream(vnode, options);
stream.on('error', (err) => {
reject(err);
});
stream.pipe(
new Writable({
write(chunk, _encoding, callback) {
html += chunk.toString('utf-8');
callback();
},
destroy() {
resolve(html);
},
})
);
});
}
/**
* Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues
* See https://github.com/facebook/react/issues/24169
*/
async function readResult(stream) {
const reader = stream.getReader();
let result = '';
const decoder = new TextDecoder('utf-8');
while (true) {
const { done, value } = await reader.read();
if (done) {
if (value) {
result += decoder.decode(value);
} else {
// This closes the decoder
decoder.decode(new Uint8Array());
}
return result;
}
result += decoder.decode(value, { stream: true });
}
}
async function renderToReadableStreamAsync(vnode, options) {
return await readResult(await ReactDOM.renderToReadableStream(vnode, options));
}
const _renderer0 = {
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,
};
const renderers = [Object.assign({"name":"@astrojs/react","clientEntrypoint":"@astrojs/react/client.js","serverEntrypoint":"@astrojs/react/server.rs.js"}, { ssr: _renderer0 }),];
export { renderers };