Compare commits
13 Commits
2d9e952040
...
bc7153a060
Author | SHA1 | Date |
---|---|---|
Aravinth Manivannan | bc7153a060 | |
Aravinth Manivannan | ef55697879 | |
Aravinth Manivannan | 1f1b21baac | |
Aravinth Manivannan | f56ca02d39 | |
Aravinth Manivannan | 9236451628 | |
Aravinth Manivannan | 330e835094 | |
Aravinth Manivannan | 6660602ab6 | |
Aravinth Manivannan | cdeabb06aa | |
Aravinth Manivannan | cbcd7bad7b | |
Aravinth Manivannan | 7dca981ee3 | |
Aravinth Manivannan | eae0a568e7 | |
Aravinth Manivannan | 21bea52323 | |
Aravinth Manivannan | 0f77f81f84 |
|
@ -13,7 +13,7 @@ ip= "0.0.0.0"
|
|||
# Minimum of two threads are advisable for top async performance but can work
|
||||
# with one also.
|
||||
workers = 2
|
||||
domain = "demo.librepages.org"
|
||||
domain = "localhost"
|
||||
cookie_secret = "94b2b2732626fdb7736229a7c777cb451e6304c147c4549f30"
|
||||
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ CREATE TABLE IF NOT EXISTS librepages_sites (
|
|||
repo_url VARCHAR(3000) NOT NULL,
|
||||
branch TEXT NOT NULL,
|
||||
hostname VARCHAR(3000) NOT NULL UNIQUE,
|
||||
pub_id uuid NOT NULL UNIQUE,
|
||||
ID SERIAL PRIMARY KEY NOT NULL,
|
||||
owned_by INTEGER NOT NULL references librepages_users(ID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX librepages_sites_site_secret ON librepages_sites(site_secret);
|
||||
CREATE UNIQUE INDEX librepages_sites_site_pub_id ON librepages_sites(pub_id);
|
||||
|
|
205
sqlx-data.json
205
sqlx-data.json
|
@ -1,5 +1,49 @@
|
|||
{
|
||||
"db": "PostgreSQL",
|
||||
"142525c6d4a5a32ca2419ff19d452722375927f2c1b734092a03106eafd9ef5c": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "site_secret",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "repo_url",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "branch",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hostname",
|
||||
"ordinal": 3,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "pub_id",
|
||||
"ordinal": 4,
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT site_secret, repo_url, branch, hostname, pub_id\n FROM librepages_sites\n WHERE owned_by = (SELECT ID FROM librepages_users WHERE name = $1 );\n "
|
||||
},
|
||||
"14cdc724af64942e93994f97e9eafc8272d15605eff7aab9e5177d01f2bf6118": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
@ -15,44 +59,6 @@
|
|||
},
|
||||
"query": "INSERT INTO librepages_site_deploy_events\n (event_type, time, site, pub_id) VALUES (\n (SELECT iD from librepages_deploy_event_type WHERE name = $1),\n $2,\n (SELECT ID from librepages_sites WHERE hostname = $3),\n $4\n );\n "
|
||||
},
|
||||
"1ac91b492001493430c686d9cd7d6be03ada4b4c431d7bc112ef2105eba0e82d": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "repo_url",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "branch",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hostname",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "owned_by",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT repo_url, branch, hostname, owned_by\n FROM librepages_sites\n WHERE site_secret = $1\n "
|
||||
},
|
||||
"1be33ea4fe0e6079c88768ff912b824f4b0250193f2d086046c1fd0da125ae0c": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
@ -190,22 +196,6 @@
|
|||
},
|
||||
"query": "DELETE FROM librepages_users WHERE name = ($1)"
|
||||
},
|
||||
"67311c6196639edd153b7b7dd56a37703b67abe750b88f5afdcf0d3d779432e7": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO librepages_sites\n (site_secret, repo_url, branch, hostname, owned_by)\n VALUES ($1, $2, $3, $4, ( SELECT ID FROM librepages_users WHERE name = $5 ));\n "
|
||||
},
|
||||
"6a557f851d4f47383b864085093beb0954e79779f21b655978f07e285281e0ac": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
@ -252,7 +242,7 @@
|
|||
},
|
||||
"query": "UPDATE librepages_users set password = $1\n WHERE name = $2"
|
||||
},
|
||||
"9fd163d10860ad4519f9398582aaa0615d6d7b784e844ee71038f77dcd069eed": {
|
||||
"aed3f57305201a19739dfb11cf8780036b992925100de98f0697d76dbea4df65": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -274,47 +264,15 @@
|
|||
"name": "hostname",
|
||||
"ordinal": 3,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "pub_id",
|
||||
"ordinal": 4,
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT site_secret, repo_url, branch, hostname\n FROM librepages_sites\n WHERE owned_by = (SELECT ID FROM librepages_users WHERE name = $1 );\n "
|
||||
},
|
||||
"aad26d1f932001cbe49b147348aa528eca5101ec6ef83cb034e1ccd0dbd17878": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "site_secret",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "repo_url",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "branch",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hostname",
|
||||
"ordinal": 3,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
|
@ -327,7 +285,7 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT site_secret, repo_url, branch, hostname\n FROM librepages_sites\n WHERE owned_by = (SELECT ID FROM librepages_users WHERE name = $1 )\n AND hostname = $2;\n "
|
||||
"query": "SELECT site_secret, repo_url, branch, hostname, pub_id\n FROM librepages_sites\n WHERE owned_by = (SELECT ID FROM librepages_users WHERE name = $1 )\n AND hostname = $2;\n "
|
||||
},
|
||||
"b48c77db6e663d97df44bf9ec2ee92fd3e02f2dcbcdbd1d491e09fab2da68494": {
|
||||
"describe": {
|
||||
|
@ -355,6 +313,23 @@
|
|||
},
|
||||
"query": "SELECT name, password FROM librepages_users WHERE email = ($1)"
|
||||
},
|
||||
"b8b1b3c5fa205b071f577b2ce9993ddfc7c99ada26aea48aa1c201c8c3c7fcf6": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO librepages_sites\n (site_secret, repo_url, branch, hostname, pub_id, owned_by)\n VALUES ($1, $2, $3, $4, $5, ( SELECT ID FROM librepages_users WHERE name = $6 ));\n "
|
||||
},
|
||||
"bdd4d2a1b0b97ebf8ed61cfd120b40146fbf3ea9afb5cd0e03c9d29860b6a26b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
@ -395,6 +370,50 @@
|
|||
},
|
||||
"query": "SELECT email FROM librepages_users WHERE name = $1"
|
||||
},
|
||||
"d132d22a214356474d450afc148ae913080119a99e37e089f5df281da151426e": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "repo_url",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "branch",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hostname",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "owned_by",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "pub_id",
|
||||
"ordinal": 4,
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT repo_url, branch, hostname, owned_by, pub_id\n FROM librepages_sites\n WHERE site_secret = $1\n "
|
||||
},
|
||||
"d2327c1bcb40e18518c2112413a19a9b26eb0f54f83c53e968c9752d70c8dd4e": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
|
|
@ -41,12 +41,14 @@ impl AddSite {
|
|||
fn to_site(self, s: &Settings) -> Site {
|
||||
let site_secret = get_random(32);
|
||||
let hostname = get_random_subdomain(s);
|
||||
let pub_id = Uuid::new_v4();
|
||||
Site {
|
||||
site_secret,
|
||||
repo_url: self.repo_url,
|
||||
branch: self.branch,
|
||||
hostname,
|
||||
owner: self.owner,
|
||||
pub_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
21
src/db.rs
21
src/db.rs
|
@ -256,13 +256,14 @@ impl Database {
|
|||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO librepages_sites
|
||||
(site_secret, repo_url, branch, hostname, owned_by)
|
||||
VALUES ($1, $2, $3, $4, ( SELECT ID FROM librepages_users WHERE name = $5 ));
|
||||
(site_secret, repo_url, branch, hostname, pub_id, owned_by)
|
||||
VALUES ($1, $2, $3, $4, $5, ( SELECT ID FROM librepages_users WHERE name = $6 ));
|
||||
",
|
||||
msg.site_secret,
|
||||
msg.repo_url,
|
||||
msg.branch,
|
||||
msg.hostname,
|
||||
msg.pub_id,
|
||||
msg.owner,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
|
@ -278,11 +279,12 @@ impl Database {
|
|||
branch: String,
|
||||
hostname: String,
|
||||
owned_by: i32,
|
||||
pub_id: Uuid,
|
||||
}
|
||||
|
||||
let site = sqlx::query_as!(
|
||||
S,
|
||||
"SELECT repo_url, branch, hostname, owned_by
|
||||
"SELECT repo_url, branch, hostname, owned_by, pub_id
|
||||
FROM librepages_sites
|
||||
WHERE site_secret = $1
|
||||
",
|
||||
|
@ -310,6 +312,7 @@ impl Database {
|
|||
hostname: site.hostname,
|
||||
owner: owner.name,
|
||||
repo_url: site.repo_url,
|
||||
pub_id: site.pub_id,
|
||||
};
|
||||
|
||||
Ok(site)
|
||||
|
@ -318,7 +321,7 @@ impl Database {
|
|||
pub async fn get_site(&self, owner: &str, hostname: &str) -> ServiceResult<Site> {
|
||||
let site = sqlx::query_as!(
|
||||
InnerSite,
|
||||
"SELECT site_secret, repo_url, branch, hostname
|
||||
"SELECT site_secret, repo_url, branch, hostname, pub_id
|
||||
FROM librepages_sites
|
||||
WHERE owned_by = (SELECT ID FROM librepages_users WHERE name = $1 )
|
||||
AND hostname = $2;
|
||||
|
@ -338,7 +341,7 @@ impl Database {
|
|||
pub async fn list_all_sites(&self, owner: &str) -> ServiceResult<Vec<Site>> {
|
||||
let mut sites = sqlx::query_as!(
|
||||
InnerSite,
|
||||
"SELECT site_secret, repo_url, branch, hostname
|
||||
"SELECT site_secret, repo_url, branch, hostname, pub_id
|
||||
FROM librepages_sites
|
||||
WHERE owned_by = (SELECT ID FROM librepages_users WHERE name = $1 );
|
||||
",
|
||||
|
@ -406,7 +409,7 @@ impl Database {
|
|||
|
||||
async fn create_event_type(&self) -> ServiceResult<()> {
|
||||
for e in &*EVENTS {
|
||||
if !self.event_type_exists(&e).await? {
|
||||
if !self.event_type_exists(e).await? {
|
||||
sqlx::query!(
|
||||
"INSERT INTO librepages_deploy_event_type
|
||||
(name) VALUES ($1) ON CONFLICT (name) DO NOTHING;",
|
||||
|
@ -524,6 +527,7 @@ struct InnerSite {
|
|||
repo_url: String,
|
||||
branch: String,
|
||||
hostname: String,
|
||||
pub_id: Uuid,
|
||||
}
|
||||
|
||||
impl InnerSite {
|
||||
|
@ -533,6 +537,7 @@ impl InnerSite {
|
|||
repo_url: self.repo_url,
|
||||
branch: self.branch,
|
||||
hostname: self.hostname,
|
||||
pub_id: self.pub_id,
|
||||
owner,
|
||||
}
|
||||
}
|
||||
|
@ -543,6 +548,7 @@ impl InnerSite {
|
|||
pub struct Site {
|
||||
pub site_secret: String,
|
||||
pub repo_url: String,
|
||||
pub pub_id: Uuid,
|
||||
pub branch: String,
|
||||
pub hostname: String,
|
||||
pub owner: String,
|
||||
|
@ -823,7 +829,7 @@ mod tests {
|
|||
// check if events are created
|
||||
for e in &*EVENTS {
|
||||
println!("Testing event type exists {}", e.name);
|
||||
assert!(db.event_type_exists(&e).await.unwrap());
|
||||
assert!(db.event_type_exists(e).await.unwrap());
|
||||
}
|
||||
|
||||
let p = super::Register {
|
||||
|
@ -847,6 +853,7 @@ mod tests {
|
|||
repo_url: "https://git.batsense.net/LibrePages/librepages.git".into(),
|
||||
branch: "librepages".into(),
|
||||
hostname: "db_works.tests.librepages.librepages.org".into(),
|
||||
pub_id: Uuid::new_v4(),
|
||||
owner: p.username.into(),
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ use serde::Deserialize;
|
|||
use serde::Serialize;
|
||||
#[cfg(not(test))]
|
||||
use tracing::{debug, error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::Site;
|
||||
use crate::errors::*;
|
||||
|
@ -39,6 +40,7 @@ pub struct Page {
|
|||
pub path: String,
|
||||
pub branch: String,
|
||||
pub domain: String,
|
||||
pub pub_id: Uuid,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
|
@ -52,6 +54,7 @@ impl Page {
|
|||
.to_owned(),
|
||||
domain: s.hostname,
|
||||
branch: s.branch,
|
||||
pub_id: s.pub_id,
|
||||
}
|
||||
}
|
||||
pub fn open_repo(&self) -> ServiceResult<Repository> {
|
||||
|
@ -277,6 +280,7 @@ mod tests {
|
|||
path: tmp_dir.to_str().unwrap().to_string(),
|
||||
branch: tests::BRANCH.to_string(),
|
||||
domain: "mcaptcha.org".into(),
|
||||
pub_id: Uuid::new_v4(),
|
||||
};
|
||||
|
||||
assert!(
|
||||
|
|
|
@ -93,7 +93,7 @@ pub async fn login_submit(
|
|||
.finish())
|
||||
} else {
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header((http::header::LOCATION, PAGES.home))
|
||||
.insert_header((http::header::LOCATION, PAGES.dash.home))
|
||||
.finish())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ async fn auth_works(ctx: ArcCtx) {
|
|||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
let headers = resp.headers();
|
||||
assert_eq!(headers.get(header::LOCATION).unwrap(), PAGES.home);
|
||||
assert_eq!(headers.get(header::LOCATION).unwrap(), PAGES.dash.home);
|
||||
let cookies = get_cookie!(resp);
|
||||
|
||||
// redirect after signin
|
||||
|
|
|
@ -16,14 +16,20 @@
|
|||
*/
|
||||
use std::cell::RefCell;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::http::header::ContentType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tera::Context;
|
||||
|
||||
use crate::ctx::api::v1::auth::Login as LoginPayload;
|
||||
use super::get_auth_middleware;
|
||||
use crate::db::Site;
|
||||
use crate::errors::ServiceResult;
|
||||
use crate::pages::errors::*;
|
||||
use crate::settings::Settings;
|
||||
use crate::AppCtx;
|
||||
|
||||
use super::TemplateSiteEvent;
|
||||
|
||||
pub use super::*;
|
||||
|
||||
pub const DASH_HOME: TemplateFile = TemplateFile::new("dash_home", "pages/dash/index.html");
|
||||
|
@ -32,6 +38,12 @@ pub struct Home {
|
|||
ctx: RefCell<Context>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
|
||||
pub struct TemplateSite {
|
||||
site: Site,
|
||||
last_update: Option<TemplateSiteEvent>,
|
||||
}
|
||||
|
||||
impl CtxError for Home {
|
||||
fn with_error(&self, e: &ReadableError) -> String {
|
||||
self.ctx.borrow_mut().insert(ERROR_KEY, e);
|
||||
|
@ -40,10 +52,11 @@ impl CtxError for Home {
|
|||
}
|
||||
|
||||
impl Home {
|
||||
pub fn new(settings: &Settings, payload: Option<&LoginPayload>) -> Self {
|
||||
pub fn new(settings: &Settings, sites: Option<&[TemplateSite]>) -> Self {
|
||||
let ctx = RefCell::new(context(settings));
|
||||
if let Some(payload) = payload {
|
||||
ctx.borrow_mut().insert(PAYLOAD_KEY, payload);
|
||||
if let Some(sites) = sites {
|
||||
ctx.borrow_mut().insert(PAYLOAD_KEY, sites);
|
||||
println!("{:#?}", sites)
|
||||
}
|
||||
Self { ctx }
|
||||
}
|
||||
|
@ -53,21 +66,78 @@ impl Home {
|
|||
.render(DASH_HOME.name, &self.ctx.borrow())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn page(s: &Settings) -> String {
|
||||
let p = Self::new(s, None);
|
||||
p.render()
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web_codegen_const_routes::get(path = "PAGES.dash.home")]
|
||||
#[tracing::instrument(name = "Dashboard homepage", skip(ctx))]
|
||||
pub async fn get_home(ctx: AppCtx) -> impl Responder {
|
||||
let home = Home::page(&ctx.settings);
|
||||
async fn get_site_data(ctx: &AppCtx, id: &Identity) -> ServiceResult<Vec<TemplateSite>> {
|
||||
let db_sites = ctx.db.list_all_sites(&id.identity().unwrap()).await?;
|
||||
let mut sites = Vec::with_capacity(db_sites.len());
|
||||
for site in db_sites {
|
||||
// TODO: impl method on DB to get latest "update" event
|
||||
let mut events = ctx.db.list_all_site_events(&site.hostname).await?;
|
||||
let last_update = events.pop().map(|event| event.into());
|
||||
sites.push(TemplateSite { site, last_update });
|
||||
}
|
||||
Ok(sites)
|
||||
}
|
||||
|
||||
#[actix_web_codegen_const_routes::get(path = "PAGES.dash.home", wrap = "get_auth_middleware()")]
|
||||
#[tracing::instrument(name = "Dashboard homepage", skip(ctx, id))]
|
||||
pub async fn get_home(ctx: AppCtx, id: Identity) -> PageResult<impl Responder, Home> {
|
||||
let sites = get_site_data(&ctx, &id)
|
||||
.await
|
||||
.map_err(|e| PageError::new(Home::new(&ctx.settings, None), e))?;
|
||||
let home = Home::new(&ctx.settings, Some(&sites)).render();
|
||||
let html = ContentType::html();
|
||||
HttpResponse::Ok().content_type(html).body(home)
|
||||
Ok(HttpResponse::Ok().content_type(html).body(home))
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_home);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use crate::ctx::ArcCtx;
|
||||
use crate::tests;
|
||||
use crate::*;
|
||||
|
||||
use super::PAGES;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn postgres_dash_home_works() {
|
||||
let (_, ctx) = tests::get_ctx().await;
|
||||
dashboard_home_works(ctx.clone()).await;
|
||||
}
|
||||
|
||||
async fn dashboard_home_works(ctx: ArcCtx) {
|
||||
const NAME: &str = "testdashuser";
|
||||
const EMAIL: &str = "testdashuser@foo.com";
|
||||
const PASSWORD: &str = "longpassword";
|
||||
|
||||
let _ = ctx.delete_user(NAME, PASSWORD).await;
|
||||
let (_, signin_resp) = ctx.register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(ctx).await;
|
||||
|
||||
let resp = get_request!(&app, PAGES.dash.home, cookies.clone());
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let res = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap();
|
||||
println!("before adding site: {res}");
|
||||
assert!(res.contains("Nothing to show"));
|
||||
|
||||
let page = ctx.add_test_site(NAME.into()).await;
|
||||
|
||||
let resp = get_request!(&app, PAGES.dash.home, cookies.clone());
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let res = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap();
|
||||
println!("after adding site: {res}");
|
||||
assert!(!res.contains("Nothing here"));
|
||||
assert!(res.contains(&page.domain));
|
||||
assert!(res.contains(&page.repo));
|
||||
|
||||
let _ = ctx.delete_user(NAME, PASSWORD).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,15 +15,43 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use actix_web::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use super::get_auth_middleware;
|
||||
pub use super::{context, Footer, TemplateFile, PAGES, PAYLOAD_KEY, TEMPLATES};
|
||||
|
||||
mod home;
|
||||
use crate::db::Event;
|
||||
use crate::db::LibrePagesEvent;
|
||||
|
||||
pub mod home;
|
||||
pub mod sites;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TemplateSiteEvent {
|
||||
pub event_type: Event,
|
||||
pub time: i64,
|
||||
pub site: String,
|
||||
pub id: Uuid,
|
||||
}
|
||||
|
||||
impl From<LibrePagesEvent> for TemplateSiteEvent {
|
||||
fn from(e: LibrePagesEvent) -> Self {
|
||||
Self {
|
||||
event_type: e.event_type,
|
||||
time: e.time.unix_timestamp(),
|
||||
site: e.site,
|
||||
id: e.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_templates(t: &mut tera::Tera) {
|
||||
home::DASH_HOME.register(t).expect(home::DASH_HOME.name);
|
||||
sites::register_templates(t);
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
home::services(cfg);
|
||||
sites::services(cfg);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::cell::RefCell;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::http::header::ContentType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tera::Context;
|
||||
|
||||
use super::get_auth_middleware;
|
||||
use crate::ctx::api::v1::pages::AddSite;
|
||||
use crate::pages::errors::*;
|
||||
use crate::settings::Settings;
|
||||
use crate::AppCtx;
|
||||
|
||||
pub use super::*;
|
||||
|
||||
pub const DASH_SITE_ADD: TemplateFile =
|
||||
TemplateFile::new("dash_site_add", "pages/dash/sites/add.html");
|
||||
|
||||
pub struct Add {
|
||||
ctx: RefCell<Context>,
|
||||
}
|
||||
|
||||
impl CtxError for Add {
|
||||
fn with_error(&self, e: &ReadableError) -> String {
|
||||
self.ctx.borrow_mut().insert(ERROR_KEY, e);
|
||||
self.render()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add {
|
||||
pub fn new(settings: &Settings) -> Self {
|
||||
let ctx = RefCell::new(context(settings));
|
||||
Self { ctx }
|
||||
}
|
||||
|
||||
pub fn render(&self) -> String {
|
||||
TEMPLATES
|
||||
.render(DASH_SITE_ADD.name, &self.ctx.borrow())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web_codegen_const_routes::get(path = "PAGES.dash.site.add", wrap = "get_auth_middleware()")]
|
||||
#[tracing::instrument(name = "Dashboard add site webpage", skip(ctx))]
|
||||
pub async fn get_add_site(ctx: AppCtx) -> PageResult<impl Responder, Add> {
|
||||
let add = Add::new(&ctx.settings).render();
|
||||
let html = ContentType::html();
|
||||
Ok(HttpResponse::Ok().content_type(html).body(add))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
|
||||
/// Data required to add site
|
||||
pub struct TemplateAddSite {
|
||||
pub repo_url: String,
|
||||
pub branch: String,
|
||||
}
|
||||
|
||||
#[actix_web_codegen_const_routes::post(
|
||||
path = "PAGES.dash.site.add",
|
||||
wrap = "get_auth_middleware()"
|
||||
)]
|
||||
#[tracing::instrument(name = "Post Dashboard add site webpage", skip(ctx, id))]
|
||||
pub async fn post_add_site(
|
||||
ctx: AppCtx,
|
||||
id: Identity,
|
||||
payload: web::Form<TemplateAddSite>,
|
||||
) -> PageResult<impl Responder, Add> {
|
||||
let owner = id.identity().unwrap();
|
||||
let payload = payload.into_inner();
|
||||
let msg = AddSite {
|
||||
branch: payload.branch,
|
||||
repo_url: payload.repo_url,
|
||||
owner,
|
||||
};
|
||||
let _page = ctx
|
||||
.add_site(msg)
|
||||
.await
|
||||
.map_err(|e| PageError::new(Add::new(&ctx.settings), e))?;
|
||||
|
||||
// TODO: redirect to deployment view
|
||||
|
||||
Ok(HttpResponse::Found()
|
||||
.append_header((http::header::LOCATION, PAGES.dash.home))
|
||||
.finish())
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_add_site);
|
||||
cfg.service(post_add_site);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use crate::ctx::ArcCtx;
|
||||
use crate::pages::dash::sites::add::TemplateAddSite;
|
||||
use crate::tests;
|
||||
use crate::*;
|
||||
|
||||
use super::PAGES;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn postgres_dashboard_add_site_works() {
|
||||
let (_, ctx) = tests::get_ctx().await;
|
||||
dashboard_add_site_works(ctx.clone()).await;
|
||||
}
|
||||
|
||||
async fn dashboard_add_site_works(ctx: ArcCtx) {
|
||||
const NAME: &str = "testdashaddsiteuser";
|
||||
const EMAIL: &str = "testdashaddsiteuser@foo.com";
|
||||
const PASSWORD: &str = "longpassword";
|
||||
|
||||
let _ = ctx.delete_user(NAME, PASSWORD).await;
|
||||
let (_, signin_resp) = ctx.register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(ctx).await;
|
||||
|
||||
let resp = get_request!(&app, PAGES.dash.site.add, cookies.clone());
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let res = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap();
|
||||
assert!(res.contains("Add Site"));
|
||||
|
||||
let payload = TemplateAddSite {
|
||||
repo_url: tests::REPO_URL.into(),
|
||||
branch: tests::BRANCH.into(),
|
||||
};
|
||||
|
||||
let add_site = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, PAGES.dash.site.add, FORM)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(add_site.status(), StatusCode::FOUND);
|
||||
let headers = add_site.headers();
|
||||
assert_eq!(
|
||||
headers.get(actix_web::http::header::LOCATION).unwrap(),
|
||||
&PAGES.dash.home
|
||||
);
|
||||
|
||||
// let page = ctx.add_test_site(NAME.into()).await;
|
||||
//
|
||||
// let resp = get_request!(&app, PAGES.dash.home, cookies.clone());
|
||||
// assert_eq!(resp.status(), StatusCode::OK);
|
||||
// let res = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap();
|
||||
// println!("after adding site: {res}");
|
||||
// assert!(!res.contains("Nothing here"));
|
||||
// assert!(res.contains(&page.domain));
|
||||
// assert!(res.contains(&page.repo));
|
||||
//
|
||||
let _ = ctx.delete_user(NAME, PASSWORD).await;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use actix_web::*;
|
||||
|
||||
use super::get_auth_middleware;
|
||||
pub use super::{context, Footer, TemplateFile, PAGES, PAYLOAD_KEY, TEMPLATES};
|
||||
|
||||
pub mod add;
|
||||
|
||||
pub fn register_templates(t: &mut tera::Tera) {
|
||||
add::DASH_SITE_ADD
|
||||
.register(t)
|
||||
.expect(add::DASH_SITE_ADD.name);
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
add::services(cfg);
|
||||
}
|
|
@ -14,8 +14,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::cell::RefCell;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::http::header;
|
||||
use actix_web::*;
|
||||
|
@ -24,10 +22,8 @@ use rust_embed::RustEmbed;
|
|||
use serde::*;
|
||||
use tera::*;
|
||||
|
||||
use crate::pages::errors::*;
|
||||
use crate::settings::Settings;
|
||||
use crate::static_assets::ASSETS;
|
||||
use crate::AppCtx;
|
||||
use crate::{GIT_COMMIT_HASH, VERSION};
|
||||
|
||||
pub mod auth;
|
||||
|
@ -134,7 +130,7 @@ impl<'a> Footer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn home(ctx: AppCtx, id: &Identity) -> HttpResponse {
|
||||
pub async fn home(id: &Identity) -> HttpResponse {
|
||||
let location = if id.identity().is_some() {
|
||||
PAGES.home
|
||||
} else {
|
||||
|
@ -146,8 +142,8 @@ pub async fn home(ctx: AppCtx, id: &Identity) -> HttpResponse {
|
|||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
auth::services(cfg);
|
||||
dash::services(cfg);
|
||||
auth::services(cfg);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -169,6 +165,8 @@ mod tests {
|
|||
auth::login::LOGIN,
|
||||
auth::register::REGISTER,
|
||||
errors::ERROR_TEMPLATE,
|
||||
super::dash::home::DASH_HOME,
|
||||
super::dash::sites::add::DASH_SITE_ADD,
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
|
|
|
@ -70,13 +70,30 @@ impl Auth {
|
|||
pub struct Dash {
|
||||
/// home route
|
||||
pub home: &'static str,
|
||||
pub site: DashSite,
|
||||
}
|
||||
|
||||
impl Dash {
|
||||
/// create new instance of Dash route
|
||||
pub const fn new() -> Dash {
|
||||
let home = "/dash";
|
||||
Dash { home }
|
||||
let site = DashSite::new();
|
||||
Dash { home, site }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
/// Dashboard Site routes
|
||||
pub struct DashSite {
|
||||
/// home route
|
||||
pub add: &'static str,
|
||||
}
|
||||
|
||||
impl DashSite {
|
||||
/// create new instance of DashSite route
|
||||
pub const fn new() -> DashSite {
|
||||
let add = "/dash/site/add";
|
||||
DashSite { add }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ use actix_identity::Identity;
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use actix_web::{http::header::ContentType, web, HttpRequest, HttpResponse, Responder};
|
||||
use actix_web::{web, HttpRequest, HttpResponse, Responder};
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::pages;
|
||||
|
@ -48,7 +48,7 @@ async fn index(req: HttpRequest, ctx: AppCtx, id: Identity) -> ServiceResult<imp
|
|||
// serve meta page
|
||||
if host == ctx.settings.server.domain || host == "localhost" {
|
||||
tracing::debug!("Into home");
|
||||
return Ok(pages::home(ctx.clone(), &id).await);
|
||||
return Ok(pages::home(&id).await);
|
||||
}
|
||||
|
||||
// serve default hostname content
|
||||
|
|
|
@ -33,7 +33,7 @@ use crate::page::Page;
|
|||
use crate::settings::Settings;
|
||||
use crate::*;
|
||||
|
||||
pub const REPO_URL: &str = "http://github.com:8080/mCaptcha/website/";
|
||||
pub const REPO_URL: &str = "https://github.com/mCaptcha/website/";
|
||||
pub const BRANCH: &str = "gh-pages";
|
||||
|
||||
pub async fn get_ctx() -> (Temp, Arc<Ctx>) {
|
||||
|
|
|
@ -4,10 +4,9 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="{{ assets.css }}" />
|
||||
<title>LibrePages</title>
|
||||
<title>{% block title %} {% endblock %}</title>
|
||||
<title>{% block title %} {% endblock %} | LibrePages</title>
|
||||
</head>
|
||||
<body>
|
||||
<body class="default-body">
|
||||
<header>{% block nav %} {% endblock %}</header>
|
||||
{% block main %} {% endblock %}
|
||||
{% include "footer" %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<input type="checkbox" class="nav__toggle" id="nav__toggle" />
|
||||
|
||||
<div class="nav__header">
|
||||
<a class="nav__logo-container" href="/">
|
||||
<a class="nav__logo-container" href="{{page.dash.home}}">
|
||||
<p class="nav__home-btn">LibrePages</p>
|
||||
</a>
|
||||
<label class="nav__hamburger-menu" for="nav__toggle">
|
||||
|
@ -14,13 +14,8 @@
|
|||
|
||||
<div class="nav__link-group">
|
||||
<div class="nav__link-container">
|
||||
<a class="nav__link" rel="noreferrer" href="{{ page.gist.new }}">New Paste</a>
|
||||
<a class="nav__link" rel="noreferrer" href="{{ page.dash.site.add }}">New Site</a>
|
||||
</div>
|
||||
{% if loggedin_user %}
|
||||
<div class="nav__link-container">
|
||||
<a class="nav__link" rel="noreferrer" href="{{ loggedin_user }}">Profile</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="nav__link-container">
|
||||
<a class="nav__link" rel="noreferrer" href="{{ page.auth.logout }}">Log out</a>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<input type="checkbox" class="nav__toggle" id="nav__toggle" />
|
||||
|
||||
<div class="nav__header">
|
||||
<a class="nav__logo-container" href="/">
|
||||
<a class="nav__logo-container" href="{{ page.dash.home }}">
|
||||
<p class="nav__home-btn">LibrePages</p>
|
||||
</a>
|
||||
<label class="nav__hamburger-menu" for="nav__toggle">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@mixin fullscreen {
|
||||
height: 100vh;
|
||||
min-height: 500px;
|
||||
max-height: 800px;
|
||||
// max-height: 800px;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
@import "defaults.scss";
|
||||
@import "pages/auth/sass/main.scss";
|
||||
@import "pages/auth/sass/form/main.scss";
|
||||
@import "pages/dash/main.scss";
|
||||
@import "pages/dash/sites/main.scss";
|
||||
@import "components/sass/footer/main.scss";
|
||||
@import "components/nav/sass/main.scss";
|
||||
|
||||
.default-body {
|
||||
display: flex;
|
||||
@include fullscreen;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
|
|
@ -1,160 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="{{ assets.css }}" />
|
||||
<title>LibrePages</title>
|
||||
</head>
|
||||
<body class="auth__body">
|
||||
<header>
|
||||
<nav>
|
||||
<p>LibrePages</p>
|
||||
<span class="nav__spacer"></span>
|
||||
<ul class="nav__links">
|
||||
<li class="nav__item">Help</li>
|
||||
<li class="nav__item">Settings</li>
|
||||
<li class="nav__item">Logout</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<div class="sites__collection">
|
||||
<div class="sites__actions">
|
||||
<a class="sites__actions__new-site" href="/add/new/site">
|
||||
<button>Add new site</button>
|
||||
</a>
|
||||
</div>
|
||||
<a href="/sites/mcaptcha.org" class="site__container">
|
||||
{% extends 'base' %}{% block title %} Dashboard{% endblock title %} {% block nav
|
||||
%} {% include "auth_nav" %} {% endblock nav %} {% block main %}
|
||||
|
||||
<main class="sites__main">
|
||||
<div class="sites__collection">
|
||||
<div class="sites__actions">
|
||||
<a class="sites__actions__new-site" href="{{ page.dash.site.add }}">
|
||||
<button>Add New Site</button>
|
||||
</a>
|
||||
</div>
|
||||
{% if payload|length > 0 %}
|
||||
{% for deployment in payload %}
|
||||
<a href="/sites/view/{{ deployment.site.hostname }}" class="site__container">
|
||||
<div class="site__info--head">
|
||||
<img
|
||||
class="site__container--preview"
|
||||
src="https://mcaptcha.org/favicon.ico"
|
||||
src="{{ deployment.site.hostname }}/favicon.ico"
|
||||
alt="{{ deployment.site.hostname }}'s favicon"
|
||||
/>
|
||||
<div class="site__info--column">
|
||||
<p href="https://mcaptcha.org"><b>mcaptcha.org</b></p>
|
||||
<p>Deploys from {{ source_url }}</p>
|
||||
<p><b>{{ deployment.site.hostname }}</b></p>
|
||||
<p>Deploys from {{ deployment.site.repo_url }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site__info--tail">
|
||||
<p>Last update {{ last_update }}</p>
|
||||
</div></a
|
||||
>
|
||||
</div>
|
||||
</main>
|
||||
{% include "footer" %}
|
||||
</body>
|
||||
{% if deployment.last_update %}
|
||||
<div class="site__info--tail">
|
||||
<p>Last update {{ deployment.last_update.time }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="sites__banner">Nothing to show, click <a href="{{page.dash.site.add}}">here</a> to get started!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav__spacer {
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
.nav__links {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nav__item {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.sites__collection {
|
||||
margin: auto;
|
||||
width: 70%;
|
||||
|
||||
border: 1px solid #e8ebed;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sites__actions {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0px 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.sites__actions__new-site {
|
||||
min-height: 36px;
|
||||
background: green;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0px 8px;
|
||||
}
|
||||
|
||||
.sites__actions__new-site > button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
border: none;
|
||||
width: 100%;
|
||||
color: white;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.site__container {
|
||||
box-sizing: border-box;
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site__container:hover {
|
||||
background: #f7f8f8;
|
||||
}
|
||||
|
||||
.site__info--head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site__info--column {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.site__info--column > p,
|
||||
.site__info--column > a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.site__container:visited,
|
||||
.site__container {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site__container--preview {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
</html>
|
||||
{% endblock main %}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
.sites__main {
|
||||
width: 100%;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.sites__collection {
|
||||
margin: auto;
|
||||
width: 70%;
|
||||
|
||||
border: 1px solid #e8ebed;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sites__actions {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0px 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.sites__actions__new-site {
|
||||
min-height: 36px;
|
||||
background: green;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0px 8px;
|
||||
}
|
||||
|
||||
.sites__actions__new-site > button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
border: none;
|
||||
width: 100%;
|
||||
color: white;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.site__container {
|
||||
box-sizing: border-box;
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site__container:hover {
|
||||
background: #f7f8f8;
|
||||
}
|
||||
|
||||
.site__info--head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site__info--column {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.site__info--column > p,
|
||||
.site__info--column > a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.site__container:visited,
|
||||
.site__container {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site__container--preview {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.sites__banner {
|
||||
text-align: center;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{% extends 'base' %}{% block title %} Add Site{% endblock title %} {% block nav
|
||||
%} {% include "auth_nav" %} {% endblock nav %} {% block main %}
|
||||
|
||||
<main class="sites__main">
|
||||
<div class="add-site__container">
|
||||
<form class="auth-form" action="{{ page.dash.site.add }}" method="POST">
|
||||
<label class="auth-form__label" for="repo_url">
|
||||
Repository URL
|
||||
<input
|
||||
type="url"
|
||||
name="repo_url"
|
||||
id="repo_url"
|
||||
class="auth-form__input"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="auth-form__label" for="branch">
|
||||
Deployment branch
|
||||
<input type="text" name="branch" id="branch" class="auth-form__input" />
|
||||
</label>
|
||||
|
||||
<div class="auth-form__action-container">
|
||||
<button class="auth-form__submit" type="submit">Add Site</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{% endblock main %}
|
|
@ -0,0 +1,4 @@
|
|||
.add-site__container {
|
||||
max-width: 50%;
|
||||
margin: auto;
|
||||
}
|
Loading…
Reference in New Issue