Compare commits

...

5 Commits

72 changed files with 4856 additions and 13 deletions

View File

@ -148,7 +148,7 @@ pub enum NetworkError {
/// Failed to retrieve connection state of wlan0 interface.
WlanOperstate(IoError),
/// Failed to save wpa_supplicant configuration changes to file.
Save,
Save(IoError),
/// Failed to connect to network.
Connect {
/// ID.
@ -197,7 +197,7 @@ impl std::error::Error for NetworkError {
NetworkError::Delete { .. } => None,
NetworkError::WlanState(ref source) => Some(source),
NetworkError::WlanOperstate(ref source) => Some(source),
NetworkError::Save => None,
NetworkError::Save(ref source) => Some(source),
NetworkError::Connect { .. } => None,
NetworkError::StartInterface { ref source, .. } => Some(source),
NetworkError::WpaCtrl(ref source) => Some(source),
@ -326,7 +326,11 @@ impl std::fmt::Display for NetworkError {
NetworkError::WlanOperstate(_) => {
write!(f, "Failed to retrieve connection state of wlan0 interface")
}
NetworkError::Save => write!(f, "Failed to save configuration changes to file"),
NetworkError::Save(ref source) => write!(
f,
"Failed to save configuration changes to file: {}",
source
),
NetworkError::Connect { ref id, ref iface } => {
write!(
f,

View File

@ -513,16 +513,14 @@ pub fn add(wlan_iface: &str, ssid: &str, pass: &str) -> Result<(), NetworkError>
// append wpa_passphrase output to wpa_supplicant-<wlan_iface>.conf if successful
if output.status.success() {
// open file in append mode
let file = OpenOptions::new().append(true).open(wlan_config);
let mut file = OpenOptions::new()
.append(true)
.open(wlan_config)
// TODO: create the file if it doesn't exist
.map_err(NetworkError::Save)?;
file.write(&wpa_details).map_err(NetworkError::Save)?;
let _file = match file {
// if file exists & open succeeds, write wifi configuration
Ok(mut f) => f.write(&wpa_details),
// TODO: handle this better: create file if not found
// & seed with 'ctrl_interace' & 'update_config' settings
// config file could also be copied from peach/config fs location
Err(e) => panic!("Failed to write to file: {}", e),
};
Ok(())
} else {
let err_msg = String::from_utf8_lossy(&output.stdout);
@ -642,6 +640,38 @@ pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
Ok(())
}
/// Forget credentials for the given network SSID and interface.
/// Look up the network identified for the given SSID, delete the credentials
/// and then save.
///
/// # Arguments
///
/// * `iface` - A string slice holding the name of a wireless network interface
/// * `ssid` - A string slice holding the SSID for a wireless access point
///
/// If the credentials are successfully deleted and saved, an `Ok` `Result`
/// type is returned. In the event of an error, a `NetworkError` is returned
/// in the `Result`.
pub fn forget(iface: &str, ssid: &str) -> Result<(), NetworkError> {
// get the id of the network
let id_opt = id(iface, ssid)?;
let id = id_opt.ok_or(NetworkError::Id {
ssid: ssid.to_string(),
iface: iface.to_string(),
})?;
// delete the old credentials
// TODO: i've switched these back to the "correct" order
// WEIRD BUG: the parameters below are technically in the wrong order:
// it should be id first and then iface, but somehow they get twisted.
// i don't understand computers.
//delete(&iface, &id)?;
delete(&id, iface)?;
// save the updates to wpa_supplicant.conf
save()?;
Ok(())
}
/// Modify password for a given network identifier and interface.
///
/// # Arguments
@ -708,7 +738,7 @@ pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
/// Save configuration updates to the `wpa_supplicant` configuration file.
///
/// If wireless network configuration updates are successfully save to the
/// If wireless network configuration updates are successfully saved to the
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
/// event of an error, a `NetworkError` is returned in the `Result`.
pub fn save() -> Result<(), NetworkError> {
@ -716,3 +746,18 @@ pub fn save() -> Result<(), NetworkError> {
wpa.request("SAVE_CONFIG")?;
Ok(())
}
/// Update password for an access point and save configuration updates to the
/// `wpa_supplicant` configuration file.
///
/// If wireless network configuration updates are successfully saved to the
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
/// event of an error, a `NetworkError` is returned in the `Result`.
pub fn update(iface: &str, ssid: &str, pass: &str) -> Result<(), NetworkError> {
// delete the old credentials and save the changes
forget(iface, ssid)?;
// add the new credentials
add(iface, ssid, pass)?;
reconfigure()?;
Ok(())
}

15
peach-web-lite/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "peach-web-lite"
version = "0.1.0"
edition = "2021"
[dependencies]
env_logger = "0.9.0"
lazy_static = "1.4.0"
log = "0.4.14"
maud = "0.23.0"
peach-lib = { path = "../peach-lib" }
peach-network = { path = "../peach-network" }
peach-stats = { path = "../peach-stats" }
rouille = "3.5.0"
golgi = { path = "/home/glyph/Projects/playground/rust/golgi" }

24
peach-web-lite/README.md Normal file
View File

@ -0,0 +1,24 @@
# peach-web (lite)
A web interface for managing a Scuttlebutt pub.
## Application Structure
The application is divided between the route handlers (`src/routes`), context-builders (`src/context`), templates (`src/templates`) and authentication helper functions. The web server itself is initialised in `src/main.rs`. The context-builders are responsible for retrieving data which is required by the templates. This allows separation of data retrieval and data representation (the job of the templates).
## Built With
[Rouille](https://crates.io/crates/rouille): "a Rust web micro-framework".
[maud](https://crates.io/crates/maud): "Compile-time HTML templates".
[golgi](https://git.coopcloud.tech/golgi-ssb/golgi): "an experimental Scuttlebutt client library".
[peach-lib](https://git.coopcloud.tech/PeachCloud/peach-workspace/src/branch/main/peach-lib).
[peach-network](https://git.coopcloud.tech/PeachCloud/peach-workspace/src/branch/main/peach-network).
[peach-stats](https://git.coopcloud.tech/PeachCloud/peach-workspace/src/branch/main/peach-stats)
## Design Goals
Be lean and fast.
## Licensing
AGPL-3.0

68
peach-web-lite/ap_card Normal file
View File

@ -0,0 +1,68 @@
{%- if ap_state == "up" %}
<!-- NETWORK CARD -->
<div class="card center">
<!-- NETWORK INFO BOX -->
<div class="capsule capsule-container success-border">
<!-- NETWORK STATUS GRID -->
<div class="two-grid" title="PeachCloud network mode and status">
<!-- top-right config icon -->
<a class="link two-grid-top-right" href="/settings/network" title="Configure network settings">
<img id="configureNetworking" class="icon-small" src="/icons/cog.svg" alt="Configure">
</a>
<!-- left column -->
<!-- network mode icon with label -->
<div class="grid-column-1">
<img id="netModeIcon" class="center icon icon-active" src="/icons/router.svg" alt="WiFi router">
<label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="Access Point Online">ONLINE</label>
</div>
<!-- right column -->
<!-- network mode, ssid & ip with labels -->
<div class="grid-column-2">
<label class="label-small font-gray" for="netMode" title="Network Mode">MODE</label>
<p id="netMode" class="card-text" title="Network Mode">Access Point</p>
<label class="label-small font-gray" for="netSsid" title="Access Point SSID">SSID</label>
<p id="netSsid" class="card-text" title="SSID">peach</p>
<label class="label-small font-gray" for="netIp" title="Access Point IP Address">IP</label>
<p id="netIp" class="card-text" title="IP">{{ ap_ip }}</p>
</div>
</div>
<!-- horizontal dividing line -->
<hr>
<!-- DEVICES AND TRAFFIC GRID -->
<div class="three-grid card-container">
<div class="stack">
<img id="devices" class="icon icon-medium" title="Connected devices" src="/icons/devices.svg" alt="Digital devices">
<div class="flex-grid" style="padding-top: 0.5rem;">
<label class="label-medium" for="devices" style="padding-right: 3px;" title="Number of connected devices"></label>
</div>
<label class="label-small font-gray">DEVICES</label>
</div>
<div class="stack">
<img id="dataDownload" class="icon icon-medium" title="Download" src="/icons/down-arrow.svg" alt="Download">
<div class="flex-grid" style="padding-top: 0.5rem;">
{%- if ap_traffic -%}
<label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total in {{ ap_traffic.rx_unit }}">{{ ap_traffic.received }}</label>
<label class="label-small font-near-black">{{ ap_traffic.rx_unit }}</label>
{%- else -%}
<label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total"></label>
<label class="label-small font-near-black"></label>
{%- endif -%}
</div>
<label class="label-small font-gray">DOWNLOAD</label>
</div>
<div class="stack">
<img id="dataUpload" class="icon icon-medium" title="Upload" src="/icons/up-arrow.svg" alt="Upload">
<div class="flex-grid" style="padding-top: 0.5rem;">
{%- if ap_traffic -%}
<label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total in {{ ap_traffic.tx_unit }}">{{ ap_traffic.transmitted }}</label>
<label class="label-small font-near-black">{{ ap_traffic.tx_unit }}</label>
{%- else -%}
<label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total"></label>
<label class="label-small font-near-black"></label>
{%- endif -%}
</div>
<label class="label-small font-gray">UPLOAD</label>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,12 @@
[ peach-web-lite ]
230 total dependencies
Finished release [optimized] target(s) in 35.12s
[ peach-web ]
522 total dependencies
Finished release [optimized] target(s) in 1m 33s

View File

@ -0,0 +1,23 @@
use log::debug;
use peach_lib::password_utils;
use crate::error::PeachWebError;
/// Password save form request handler. This function is for use by a user who is already logged in to change their password.
pub fn save_password_form(
current_password: String,
new_password1: String,
new_password2: String,
) -> Result<(), PeachWebError> {
debug!("attempting to change password");
password_utils::verify_password(&current_password)?;
// if the previous line did not throw an error, then the old password is correct
password_utils::validate_new_passwords(&new_password1, &new_password2)?;
// if the previous line did not throw an error, then the new password is valid
password_utils::set_new_password(&new_password1)?;
Ok(())
}

View File

@ -0,0 +1,8 @@
use peach_lib::config_manager;
use crate::error::PeachWebError;
pub fn ssb_admin_ids() -> Result<Vec<String>, PeachWebError> {
let peach_config = config_manager::load_peach_config()?;
Ok(peach_config.ssb_admin_ids)
}

View File

@ -0,0 +1,4 @@
pub mod admin;
pub mod network;
pub mod status;
pub mod test;

View File

@ -0,0 +1,402 @@
//! Data retrieval for the purpose of hydrating HTML templates.
use std::collections::HashMap;
use log::info;
use peach_lib::{
config_manager, dyndns_client,
error::PeachError,
jsonrpc_client_core::{Error, ErrorKind},
jsonrpc_core::types::error::ErrorCode,
};
use peach_network::{
network,
network::{Scan, Status, Traffic},
};
use crate::error::PeachWebError;
#[derive(Debug)]
pub struct AccessPoint {
pub detail: Option<Scan>,
pub signal: Option<i32>,
pub state: String,
}
pub fn ap_state() -> String {
match network::state("ap0") {
Ok(Some(state)) => state,
_ => "Interface unavailable".to_string(),
}
}
fn convert_traffic(traffic: Traffic) -> Option<IfaceTraffic> {
// modify traffic values & assign measurement unit
// based on received and transmitted values
let (rx, rx_unit) = if traffic.received > 1_047_527_424 {
// convert to GB
(traffic.received / 1_073_741_824, "GB".to_string())
} else if traffic.received > 0 {
// otherwise, convert it to MB
((traffic.received / 1024) / 1024, "MB".to_string())
} else {
(0, "MB".to_string())
};
let (tx, tx_unit) = if traffic.transmitted > 1_047_527_424 {
// convert to GB
(traffic.transmitted / 1_073_741_824, "GB".to_string())
} else if traffic.transmitted > 0 {
((traffic.transmitted / 1024) / 1024, "MB".to_string())
} else {
(0, "MB".to_string())
};
Some(IfaceTraffic {
rx,
rx_unit,
tx,
tx_unit,
})
}
#[derive(Debug)]
pub struct ConfigureDNSContext {
pub external_domain: String,
pub dyndns_subdomain: String,
pub enable_dyndns: bool,
pub is_dyndns_online: bool,
}
impl ConfigureDNSContext {
pub fn build() -> ConfigureDNSContext {
let peach_config = config_manager::load_peach_config().unwrap();
let dyndns_fulldomain = peach_config.dyn_domain;
let is_dyndns_online = dyndns_client::is_dns_updater_online().unwrap();
let dyndns_subdomain =
dyndns_client::get_dyndns_subdomain(&dyndns_fulldomain).unwrap_or(dyndns_fulldomain);
ConfigureDNSContext {
external_domain: peach_config.external_domain,
dyndns_subdomain,
enable_dyndns: peach_config.dyn_enabled,
is_dyndns_online,
}
}
}
// TODO: this should probably rather go into the appropriate `routes` file
pub fn save_dns_configuration(
external_domain: String,
enable_dyndns: bool,
dynamic_domain: String,
) -> Result<(), PeachWebError> {
// first save local configurations
config_manager::set_external_domain(&external_domain)?;
config_manager::set_dyndns_enabled_value(enable_dyndns)?;
// if dynamic dns is enabled and this is a new domain name, then register it
if enable_dyndns {
let full_dynamic_domain = dyndns_client::get_full_dynamic_domain(&dynamic_domain);
// check if this is a new domain or if its already registered
let is_new_domain = dyndns_client::check_is_new_dyndns_domain(&full_dynamic_domain);
if is_new_domain {
match dyndns_client::register_domain(&full_dynamic_domain) {
Ok(_) => {
info!("Registered new dyndns domain");
// successful update
Ok(())
}
Err(err) => {
info!("Failed to register dyndns domain: {:?}", err);
// json response for failed update
let msg: String = match err {
// TODO: make this a nest high-level match
// PeachError::JsonRpcClientCore(Error(ErrorKind::JsonRpcError(err), _))
PeachError::JsonRpcClientCore(source) => {
match source {
Error(ErrorKind::JsonRpcError(err), _state) => match err.code {
ErrorCode::ServerError(-32030) => {
format!("Error registering domain: {} was previously registered", full_dynamic_domain)
}
_ => {
format!("Failed to register dyndns domain {:?}", err)
}
},
_ => {
format!("Failed to register dyndns domain: {:?}", source)
}
}
}
_ => "Failed to register dyndns domain".to_string(),
};
Err(PeachWebError::FailedToRegisterDynDomain(msg))
}
}
}
// if the domain is already registered, then dont re-register, and just return success
else {
Ok(())
}
} else {
Ok(())
}
}
#[derive(Debug)]
pub struct IfaceTraffic {
pub rx: u64,
pub rx_unit: String,
pub tx: u64,
pub tx_unit: String,
}
#[derive(Debug)]
pub struct NetworkDetailContext {
pub saved_aps: Vec<String>,
pub wlan_ip: String,
pub wlan_networks: HashMap<String, AccessPoint>,
pub wlan_rssi: Option<String>,
pub wlan_ssid: String,
pub wlan_state: String,
pub wlan_status: Option<Status>,
pub wlan_traffic: Option<IfaceTraffic>,
}
impl NetworkDetailContext {
pub fn build() -> NetworkDetailContext {
// TODO: read this value from the config file
let wlan_iface = "wlan0".to_string();
let wlan_ip = match network::ip(&wlan_iface) {
Ok(Some(ip)) => ip,
_ => "x.x.x.x".to_string(),
};
// list of networks saved in wpa_supplicant.conf
let wlan_list = match network::saved_networks() {
Ok(Some(ssids)) => ssids,
_ => Vec::new(),
};
// list of networks saved in wpa_supplicant.conf
let saved_aps = wlan_list.clone();
let wlan_rssi = match network::rssi_percent(&wlan_iface) {
Ok(rssi) => rssi,
Err(_) => None,
};
// list of networks currently in range (online & accessible)
let wlan_scan = match network::available_networks(&wlan_iface) {
Ok(Some(networks)) => networks,
_ => Vec::new(),
};
let wlan_ssid = match network::ssid(&wlan_iface) {
Ok(Some(ssid)) => ssid,
_ => "Not connected".to_string(),
};
let wlan_state = match network::state(&wlan_iface) {
Ok(Some(state)) => state,
_ => "Interface unavailable".to_string(),
};
let wlan_status = match network::status(&wlan_iface) {
Ok(status) => status,
// interface unavailable
_ => None,
};
let wlan_traffic = match network::traffic(&wlan_iface) {
// convert bytes to mb or gb and add appropriate units
Ok(Some(traffic)) => convert_traffic(traffic),
_ => None,
};
// create a hashmap to combine wlan_list & wlan_scan without repetition
let mut wlan_networks = HashMap::new();
for ap in wlan_scan {
let ssid = ap.ssid.clone();
let rssi = ap.signal_level.clone();
// parse the string to a signed integer (for math)
let rssi_parsed = rssi.parse::<i32>().unwrap();
// perform rssi (dBm) to quality (%) conversion
let quality_percent = 2 * (rssi_parsed + 100);
let ap_detail = AccessPoint {
detail: Some(ap),
state: "Available".to_string(),
signal: Some(quality_percent),
};
wlan_networks.insert(ssid, ap_detail);
}
for network in wlan_list {
// avoid repetition by checking that ssid is not already in list
if !wlan_networks.contains_key(&network) {
let ssid = network.clone();
let net_detail = AccessPoint {
detail: None,
state: "Not in range".to_string(),
signal: None,
};
wlan_networks.insert(ssid, net_detail);
}
}
NetworkDetailContext {
saved_aps,
wlan_ip,
wlan_networks,
wlan_rssi,
wlan_ssid,
wlan_state,
wlan_status,
wlan_traffic,
}
}
}
#[derive(Debug)]
pub struct NetworkListContext {
pub ap_state: String,
pub wlan_networks: HashMap<String, String>,
pub wlan_ssid: String,
}
impl NetworkListContext {
pub fn build() -> NetworkListContext {
// TODO: read these values from the config file
let ap_iface = "ap0".to_string();
let wlan_iface = "wlan0".to_string();
//let wlan_iface = "wlp0s20f0u2".to_string();
// list of networks saved in wpa_supplicant.conf
let wlan_list = match network::saved_networks() {
Ok(Some(ssids)) => ssids,
_ => Vec::new(),
};
// list of networks currently in range (online & accessible)
let wlan_scan = match network::available_networks(&wlan_iface) {
Ok(Some(networks)) => networks,
_ => Vec::new(),
};
let wlan_ssid = match network::ssid(&wlan_iface) {
Ok(Some(ssid)) => ssid,
_ => "Not connected".to_string(),
};
// create a hashmap to combine wlan_list & wlan_scan without repetition
let mut wlan_networks = HashMap::new();
for ap in wlan_scan {
wlan_networks.insert(ap.ssid, "Available".to_string());
}
for network in wlan_list {
// insert ssid (with state) only if it doesn't already exist
wlan_networks
.entry(network)
.or_insert_with(|| "Not in range".to_string());
}
let ap_state = match network::state(&ap_iface) {
Ok(Some(state)) => state,
_ => "Interface unavailable".to_string(),
};
NetworkListContext {
ap_state,
wlan_networks,
wlan_ssid,
}
}
}
#[derive(Debug)]
pub struct NetworkStatusContext {
pub ap_ip: String,
pub ap_ssid: String,
pub ap_state: String,
pub ap_traffic: Option<IfaceTraffic>,
pub wlan_ip: String,
pub wlan_rssi: Option<String>,
pub wlan_ssid: String,
pub wlan_state: String,
pub wlan_status: Option<Status>,
pub wlan_traffic: Option<IfaceTraffic>,
}
impl NetworkStatusContext {
pub fn build() -> Self {
// TODO: read these values from config file
let ap_iface = "ap0".to_string();
let wlan_iface = "wlan0".to_string();
let ap_ip = match network::ip(&ap_iface) {
Ok(Some(ip)) => ip,
_ => "x.x.x.x".to_string(),
};
let ap_ssid = match network::ssid(&ap_iface) {
Ok(Some(ssid)) => ssid,
_ => "Not currently activated".to_string(),
};
let ap_state = match network::state(&ap_iface) {
Ok(Some(state)) => state,
_ => "Interface unavailable".to_string(),
};
let ap_traffic = match network::traffic(&ap_iface) {
// convert bytes to mb or gb and add appropriate units
Ok(Some(traffic)) => convert_traffic(traffic),
_ => None,
};
let wlan_ip = match network::ip(&wlan_iface) {
Ok(Some(ip)) => ip,
_ => "x.x.x.x".to_string(),
};
let wlan_rssi = match network::rssi_percent(&wlan_iface) {
Ok(rssi) => rssi,
_ => None,
};
let wlan_ssid = match network::ssid(&wlan_iface) {
Ok(Some(ssid)) => ssid,
_ => "Not connected".to_string(),
};
let wlan_state = match network::state(&wlan_iface) {
Ok(Some(state)) => state,
_ => "Interface unavailable".to_string(),
};
let wlan_status = match network::status(&wlan_iface) {
Ok(status) => status,
_ => None,
};
let wlan_traffic = match network::traffic(&wlan_iface) {
// convert bytes to mb or gb and add appropriate units
Ok(Some(traffic)) => convert_traffic(traffic),
_ => None,
};
NetworkStatusContext {
ap_ip,
ap_ssid,
ap_state,
ap_traffic,
wlan_ip,
wlan_rssi,
wlan_ssid,
wlan_state,
wlan_status,
wlan_traffic,
}
}
}

View File

@ -0,0 +1,76 @@
use peach_stats::{stats, stats::LoadAverage};
/// System statistics data.
pub struct StatusContext {
pub cpu_usage_percent: Option<f32>,
pub disk_usage_percent: Option<u32>,
pub disk_free: Option<u64>,
pub load_average: Option<LoadAverage>,
pub mem_usage_percent: Option<u64>,
pub mem_used: Option<u64>,
pub mem_free: Option<u64>,
pub mem_total: Option<u64>,
pub uptime: Option<u32>,
}
impl StatusContext {
pub fn build() -> StatusContext {
// convert result to Option<CpuStatPercentages>, discard any error
let cpu_usage_percent = stats::cpu_stats_percent()
.ok()
.map(|cpu| (cpu.nice + cpu.system + cpu.user).round());
let load_average = stats::load_average().ok();
let mem_stats = stats::mem_stats().ok();
let (mem_usage_percent, mem_used, mem_free, mem_total) = match mem_stats {
Some(mem) => (
Some((mem.used / mem.total) * 100),
Some(mem.used / 1024),
Some(mem.free / 1024),
Some(mem.total / 1024),
),
None => (None, None, None, None),
};
let uptime = match stats::uptime() {
Ok(secs) => {
let uptime_mins = secs / 60;
uptime_mins.to_string()
}
Err(_) => "Unavailable".to_string(),
};
// parse the uptime string to a signed integer (for math)
let uptime_parsed = uptime.parse::<u32>().ok();
let disk_usage_stats = match stats::disk_usage() {
Ok(disks) => disks,
Err(_) => Vec::new(),
};
// select only the partition we're interested in: "/"
let disk_stats = disk_usage_stats.iter().find(|disk| disk.mountpoint == "/");
let (disk_usage_percent, disk_free) = match disk_stats {
Some(disk) => (
Some(disk.used_percentage),
// calculate free disk space in megabytes
Some(disk.one_k_blocks_free / 1024),
),
None => (None, None),
};
StatusContext {
cpu_usage_percent,
disk_usage_percent,
disk_free,
load_average,
mem_usage_percent,
mem_used,
mem_free,
mem_total,
uptime: uptime_parsed,
}
}
}

View File

@ -0,0 +1,12 @@
use golgi;
use golgi::sbot::Sbot;
pub async fn test_async() -> Result<(), Box<dyn std::error::Error>> {
let mut sbot_client = Sbot::init(Some("127.0.0.1:8009".to_string()), None).await?;
let id = sbot_client.whoami().await?;
println!("whoami: {}", id);
Ok(())
}

View File

@ -0,0 +1,60 @@
//! Custom error type representing all possible error variants for peach-web.
use peach_lib::error::PeachError;
use peach_lib::{serde_json, serde_yaml};
use serde_json::error::Error as JsonError;
use serde_yaml::Error as YamlError;
/// Custom error type encapsulating all possible errors for the web application.
#[derive(Debug)]
pub enum PeachWebError {
Json(JsonError),
Yaml(YamlError),
FailedToRegisterDynDomain(String),
PeachLib { source: PeachError, msg: String },
}
impl std::error::Error for PeachWebError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
PeachWebError::Json(ref source) => Some(source),
PeachWebError::Yaml(ref source) => Some(source),
PeachWebError::FailedToRegisterDynDomain(_) => None,
PeachWebError::PeachLib { ref source, .. } => Some(source),
}
}
}
impl std::fmt::Display for PeachWebError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source),
PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source),
PeachWebError::FailedToRegisterDynDomain(ref msg) => {
write!(f, "DYN DNS error: {}", msg)
}
PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source),
}
}
}
impl From<JsonError> for PeachWebError {
fn from(err: JsonError) -> PeachWebError {
PeachWebError::Json(err)
}
}
impl From<YamlError> for PeachWebError {
fn from(err: YamlError) -> PeachWebError {
PeachWebError::Yaml(err)
}
}
impl From<PeachError> for PeachWebError {
fn from(err: PeachError) -> PeachWebError {
PeachWebError::PeachLib {
source: err,
msg: "".to_string(),
}
}
}

View File

@ -0,0 +1,42 @@
use std::env;
use lazy_static::lazy_static;
use log::info;
mod auth;
mod context;
mod error;
mod router;
mod routes;
mod templates;
lazy_static! {
// determine run-mode from env var; default to standalone mode (aka peachpub)
static ref STANDALONE_MODE: bool = match env::var("PEACH_STANDALONE_MODE") {
// parse the value to a boolean; default to true for any error
Ok(val) => val.parse().unwrap_or(true),
Err(_) => true
};
}
fn main() {
env_logger::init();
rouille::start_server("localhost:8000", move |request| {
info!("Now listening on localhost:8000");
// static file server: matches on assets in the `static` directory
if let Some(request) = request.remove_prefix("/static") {
return rouille::match_assets(&request, "static");
}
// configure the router based on run-mode
if *STANDALONE_MODE {
info!("Running in standalone mode");
router::minimal_router(request)
} else {
info!("Running in fully-featured mode");
router::complete_router(request)
}
});
}

View File

@ -0,0 +1,194 @@
use rouille::{router, Request, Response};
use crate::routes;
/// Define router for standalone mode (PeachPub).
pub fn minimal_router(request: &Request) -> Response {
router!(request,
(GET) (/) => {
routes::home::menu()
},
(GET) (/help) => {
routes::help::menu()
},
(GET) (/login) => {
routes::login::login()
},
(POST) (/login) => {
routes::login::login_post(request)
},
(GET) (/scuttlebutt/blocks) => {
routes::scuttlebutt::blocks()
},
(GET) (/scuttlebutt/follows) => {
routes::scuttlebutt::follows()
},
(GET) (/scuttlebutt/followers) => {
routes::scuttlebutt::followers()
},
(GET) (/scuttlebutt/friends) => {
routes::scuttlebutt::friends()
},
(GET) (/scuttlebutt/peers) => {
routes::scuttlebutt::peers()
},
(GET) (/scuttlebutt/private) => {
routes::scuttlebutt::private()
},
(GET) (/scuttlebutt/profile) => {
routes::scuttlebutt::profile()
},
(GET) (/settings) => {
routes::settings::menu()
},
(GET) (/settings/admin) => {
routes::settings::admin()
},
(GET) (/settings/admin/configure) => {
routes::settings::admin_configure()
},
(GET) (/settings/admin/add) => {
routes::settings::admin_add()
},
(POST) (/settings/admin/add) => {
routes::settings::admin_add_post(request)
},
(GET) (/settings/admin/change_password) => {
routes::settings::admin_change_password()
},
(POST) (/settings/admin/change_password) => {
routes::settings::admin_change_password_post(request)
},
(POST) (/settings/admin/delete) => {
routes::settings::admin_delete_post(request)
},
(GET) (/settings/scuttlebutt) => {
routes::settings::scuttlebutt()
},
(GET) (/status) => {
routes::status::status()
},
// return 404 if not match is found
_ => routes::catchers::not_found()
)
}
/// Define router for fully-featured mode (PeachCloud).
pub fn complete_router(request: &Request) -> Response {
router!(request,
(GET) (/async) => {
routes::home::async_test()
},
(GET) (/) => {
routes::home::menu()
},
(GET) (/help) => {
routes::help::menu()
},
(GET) (/login) => {
routes::login::login()
},
(POST) (/login) => {
routes::login::login_post(request)
},
(GET) (/scuttlebutt/blocks) => {
routes::scuttlebutt::blocks()
},
(GET) (/scuttlebutt/follows) => {
routes::scuttlebutt::follows()
},
(GET) (/scuttlebutt/followers) => {
routes::scuttlebutt::followers()
},
(GET) (/scuttlebutt/friends) => {
routes::scuttlebutt::friends()
},
(GET) (/scuttlebutt/peers) => {
routes::scuttlebutt::peers()
},
(GET) (/scuttlebutt/private) => {
routes::scuttlebutt::private()
},
(GET) (/scuttlebutt/profile) => {
routes::scuttlebutt::profile()
},
(GET) (/settings) => {
routes::settings::menu()
},
(GET) (/settings/admin) => {
routes::settings::admin()
},
(GET) (/settings/admin/configure) => {
routes::settings::admin_configure()
},
(GET) (/settings/admin/add) => {
routes::settings::admin_add()
},
(POST) (/settings/admin/add) => {
routes::settings::admin_add_post(request)
},
(GET) (/settings/admin/change_password) => {
routes::settings::admin_change_password()
},
(POST) (/settings/admin/change_password) => {
routes::settings::admin_change_password_post(request)
},
(POST) (/settings/admin/delete) => {
routes::settings::admin_delete_post(request)
},
(GET) (/settings/network) => {
routes::settings::network()
},
(GET) (/settings/network/wifi) => {
routes::settings::network_list_aps()
},
(POST) (/settings/network/wifi/connect) => {
routes::settings::network_connect_wifi(request)
},
(POST) (/settings/network/wifi/disconnect) => {
routes::settings::network_disconnect_wifi(request)
},
(POST) (/settings/network/wifi/forget) => {
routes::settings::network_forget_wifi(request)
},
(GET) (/settings/network/wifi/ssid/{ssid: String}) => {
routes::settings::network_detail(ssid)
},
(GET) (/settings/network/wifi/add) => {
routes::settings::network_add_ap(None)
},
(GET) (/settings/network/wifi/add/{ssid: String}) => {
routes::settings::network_add_ap(Some(ssid))
},
(POST) (/settings/network/wifi/add) => {
routes::settings::network_add_ap_post(request)
},
(GET) (/settings/network/wifi/modify) => {
routes::settings::network_modify_ap(None)
},
// TODO: see if we can use the ?= syntax for ssid param
(GET) (/settings/network/wifi/modify/{ssid: String}) => {
routes::settings::network_modify_ap(Some(ssid))
},
(POST) (/settings/network/wifi/modify) => {
routes::settings::network_modify_ap_post(request)
},
(GET) (/settings/network/dns) => {
routes::settings::network_configure_dns()
},
(POST) (/settings/network/dns) => {
routes::settings::network_configure_dns_post(request)
},
(GET) (/settings/scuttlebutt) => {
routes::settings::scuttlebutt()
},
(GET) (/status) => {
routes::status::status()
},
(GET) (/status/network) => {
routes::status::network()
},
// return 404 if not match is found
_ => routes::catchers::not_found()
)
}

View File

@ -0,0 +1,10 @@
use log::debug;
use rouille::Response;
use crate::templates;
pub fn not_found() -> Response {
debug!("received GET request for a route which is not defined");
Response::html(templates::catchers::not_found()).with_status_code(404)
}

View File

@ -0,0 +1,10 @@
use log::debug;
use rouille::Response;
use crate::templates;
pub fn menu() -> Response {
debug!("received GET request for: /help");
Response::html(templates::help::menu())
}

View File

@ -0,0 +1,14 @@
use log::debug;
use rouille::Response;
use crate::templates;
pub fn menu() -> Response {
debug!("received GET request for: /");
Response::html(templates::home::menu())
}
pub fn async_test() -> Response {
Response::html(templates::home::async_test())
}

View File

@ -0,0 +1,26 @@
use log::debug;
use rouille::{post_input, try_or_400, Request, Response};
use crate::templates;
pub fn login() -> Response {
debug!("received GET request for: /login");
Response::html(templates::login::login())
}
pub fn login_post(request: &Request) -> Response {
debug!("received POST request for: /login");
let data = try_or_400!(post_input!(request, {
username: String,
password: String,
}));
// TODO: handle authentication...
debug!("{:?}", data);
// TODO: add flash message
Response::redirect_302("/")
}

View File

@ -0,0 +1,7 @@
pub mod catchers;
pub mod help;
pub mod home;
pub mod login;
pub mod scuttlebutt;
pub mod settings;
pub mod status;

View File

@ -0,0 +1,46 @@
use log::debug;
use rouille::Response;
use crate::templates;
pub fn blocks() -> Response {
debug!("received GET request for: /scuttlebutt/blocks");
Response::html(templates::scuttlebutt::peers_list("Blocks".to_string()))
}
pub fn follows() -> Response {
debug!("received GET request for: /scuttlebutt/follows");
Response::html(templates::scuttlebutt::peers_list("Follows".to_string()))
}
pub fn followers() -> Response {
debug!("received GET request for: /scuttlebutt/followers");
Response::html(templates::scuttlebutt::peers_list("Followers".to_string()))
}
pub fn friends() -> Response {
debug!("received GET request for: /scuttlebutt/friends");
Response::html(templates::scuttlebutt::peers_list("Friends".to_string()))
}
pub fn peers() -> Response {
debug!("received GET request for: /scuttlebutt/peers");
Response::html(templates::scuttlebutt::peers())
}
pub fn private() -> Response {
debug!("received GET request for: /scuttlebutt/private");
Response::html(templates::scuttlebutt::private())
}
pub fn profile() -> Response {
debug!("received GET request for: /scuttlebutt/profile");
Response::html(templates::scuttlebutt::profile(None, None))
}

View File

@ -0,0 +1,340 @@
use log::{debug, warn};
use peach_lib::config_manager;
use peach_network::network;
use rouille::{post_input, try_or_400, Request, Response};
use crate::auth;
use crate::context;
use crate::templates;
pub fn menu() -> Response {
debug!("received GET request for: /settings");
Response::html(templates::settings::menu::menu())
}
pub fn admin() -> Response {
debug!("received GET request for: /settings/admin");
Response::html(templates::settings::admin::menu())
}
pub fn admin_configure() -> Response {
debug!("received GET request for: /settings/admin/configure");
Response::html(templates::settings::admin::configure(None, None))
}
pub fn admin_add() -> Response {
debug!("received GET request for: /settings/admin/add");
Response::html(templates::settings::admin::add(None, None))
}
pub fn admin_add_post(request: &Request) -> Response {
debug!("received POST request for: /settings/admin/add");
let data = try_or_400!(post_input!(request, { ssb_id: String }));
debug!("{:?}", data);
let (flash_name, flash_msg) = match config_manager::add_ssb_admin_id(&data.ssb_id) {
Ok(_) => (
"success".to_string(),
"Added new SSB administrator ID".to_string(),
),
Err(e) => (
"error".to_string(),
format!("Failed to add new SSB administrator ID: {}", e),
),
};
Response::html(templates::settings::admin::add(
Some(flash_msg),
Some(flash_name),
))
}
pub fn admin_change_password() -> Response {
debug!("received GET request for: /settings/admin/change_password");
Response::html(templates::settings::admin::change_password(None, None))
}
pub fn admin_change_password_post(request: &Request) -> Response {
debug!("received POST request for: /settings/admin/change_password");
let data = try_or_400!(post_input!(request, {
current_password: String,
new_password1: String,
new_password2: String
}));
// attempt to update the password
let (flash_name, flash_msg) = match auth::save_password_form(
data.current_password,
data.new_password1,
data.new_password2,
) {
Ok(_) => ("success".to_string(), "Saved new password".to_string()),
Err(e) => (
"error".to_string(),
format!("Failed to save new password: {}", e),
),
};
Response::html(templates::settings::admin::change_password(
Some(flash_msg),
Some(flash_name),
))
}
pub fn admin_delete_post(request: &Request) -> Response {
debug!("received POST request for: /settings/admin/delete");
let data = try_or_400!(post_input!(request, { ssb_id: String }));
let (flash_name, flash_msg) = match config_manager::delete_ssb_admin_id(&data.ssb_id) {
Ok(_) => (
"success".to_string(),
"Removed SSB administrator ID".to_string(),
),
Err(e) => (
"error".to_string(),
format!("Failed to remove SSB administrator ID: {}", e),
),
};
Response::html(templates::settings::admin::configure(
Some(flash_msg),
Some(flash_name),
))
}
pub fn network() -> Response {
debug!("received GET request for: /settings/network");
Response::html(templates::settings::network::menu())
}
pub fn network_add_ap(ssid: Option<String>) -> Response {
debug!("received GET request for: /settings/network/wifi/add");
Response::html(templates::settings::network::add_ap(ssid, None, None))
}
pub fn network_add_ap_post(request: &Request) -> Response {
debug!("received POST request for: /settings/network/wifi/add");
// TODO: read this value from the config file instead
let wlan_iface = "wlan0".to_string();
let data = try_or_400!(post_input!(request, { ssid: String, pass: String }));
/* ADD WIFI CREDENTIALS FOR AP */
// check if the credentials already exist for this access point
let creds_exist = match network::saved_networks() {
Ok(Some(ssids)) => ssids.contains(&data.ssid),
_ => false,
};
let (flash_name, flash_msg) = if creds_exist {
(
"error".to_string(),
"Network credentials already exist for this access point".to_string(),
)
} else {
// if credentials not found, generate and write wifi config to wpa_supplicant
match network::add(&wlan_iface, &data.ssid, &data.pass) {
Ok(_) => {
debug!("Added WiFi credentials.");
// force reread of wpa_supplicant.conf file with new credentials
match network::reconfigure() {
Ok(_) => debug!("Successfully reconfigured wpa_supplicant"),
Err(_) => warn!("Failed to reconfigure wpa_supplicant"),
}
("success".to_string(), "Added WiFi credentials".to_string())
}
Err(e) => {
debug!("Failed to add WiFi credentials.");
("error".to_string(), format!("{}", e))
}
}
};
Response::html(templates::settings::network::add_ap(
None,
Some(flash_msg),
Some(flash_name),
))
}
pub fn network_configure_dns() -> Response {
debug!("received GET request for: /settings/network/dns");
Response::html(templates::settings::network::configure_dns(None, None))
}
pub fn network_configure_dns_post(request: &Request) -> Response {
debug!("received POST request for: /settings/network/dns");
let data = try_or_400!(
post_input!(request, { external_domain: String, enable_dyndns: bool, dynamic_domain: String })
);
let (flash_name, flash_msg) = match context::network::save_dns_configuration(
data.external_domain,
data.enable_dyndns,
data.dynamic_domain,
) {
Ok(_) => (
"success".to_string(),
"New dynamic DNS configuration is now enabled".to_string(),
),
Err(e) => (
"error".to_string(),
format!("Failed to save DNS configuration: {}", e),
),
};
Response::html(templates::settings::network::configure_dns(
Some(flash_msg),
Some(flash_name),
))
}
pub fn network_modify_ap(ssid: Option<String>) -> Response {
debug!("received GET request for: /settings/network/wifi/modify");
Response::html(templates::settings::network::modify_ap(ssid, None, None))
}
pub fn network_modify_ap_post(request: &Request) -> Response {
debug!("received POST request for: /settings/network/wifi/modify");
// TODO: read this value from the config file instead
let wlan_iface = "wlan0".to_string();
let data = try_or_400!(post_input!(request, { ssid: String, pass: String }));
/* MODIFY WIFI CREDENTIALS FOR AP */
let (flash_name, flash_msg) = match network::update(&wlan_iface, &data.ssid, &data.pass) {
Ok(_) => ("success".to_string(), "WiFi password updated".to_string()),
Err(e) => (
"error".to_string(),
format!("Failed to update WiFi password: {}", e),
),
};
Response::html(templates::settings::network::modify_ap(
None,
Some(flash_msg),
Some(flash_name),
))
}
pub fn network_connect_wifi(request: &Request) -> Response {
debug!("received POST request for: /settings/network/wifi/connect");
// TODO: read this value from the config file instead
let wlan_iface = "wlan0".to_string();
let data = try_or_400!(post_input!(request, { ssid: String }));
let (flash_name, flash_msg) = match network::id(&wlan_iface, &data.ssid) {
Ok(Some(id)) => match network::connect(&id, &wlan_iface) {
Ok(_) => (
"success".to_string(),
"Connected to chosen network".to_string(),
),
Err(e) => (
"error".to_string(),
format!("Failed to connect to chosen network: {}", e),
),
},
_ => (
"error".to_string(),
"Failed to retrieve the network ID".to_string(),
),
};
Response::html(templates::settings::network::network_detail(
data.ssid,
Some(flash_msg),
Some(flash_name),
))
}
pub fn network_disconnect_wifi(request: &Request) -> Response {
debug!("received POST request for: /settings/network/wifi/disconnect");
// TODO: read this value from the config file instead
let wlan_iface = "wlan0".to_string();
let data = try_or_400!(post_input!(request, { ssid: String }));
let (flash_name, flash_msg) = match network::disable(&wlan_iface, &data.ssid) {
Ok(_) => (
"success".to_string(),
"Disconnected from WiFi network".to_string(),
),
Err(e) => (
"error".to_string(),
format!("Failed to disconnect from WiFi network: {}", e),
),
};
Response::html(templates::settings::network::network_detail(
data.ssid,
Some(flash_msg),
Some(flash_name),
))
}
pub fn network_forget_wifi(request: &Request) -> Response {
debug!("received POST request for: /settings/network/wifi/forget");
// TODO: read this value from the config file instead
let wlan_iface = "wlan0".to_string();
let data = try_or_400!(post_input!(request, { ssid: String }));
let (flash_name, flash_msg) = match network::forget(&wlan_iface, &data.ssid) {
Ok(_) => (
"success".to_string(),
"WiFi credentials removed".to_string(),
),
Err(e) => (
"error".to_string(),
format!("Failed to remove WiFi credentials: {}", e),
),
};
Response::html(templates::settings::network::network_detail(
data.ssid,
Some(flash_msg),
Some(flash_name),
))
}
pub fn network_detail(ssid: String) -> Response {
debug!("received GET request for: /settings/network/wifi/<selected>");
Response::html(templates::settings::network::network_detail(
ssid, None, None,
))
}
pub fn network_list_aps() -> Response {
debug!("received GET request for: /settings/network/wifi");
Response::html(templates::settings::network::list_aps())
}
pub fn scuttlebutt() -> Response {
debug!("received GET request for: /settings/scuttlebutt");
Response::html(templates::settings::scuttlebutt::scuttlebutt())
}

View File

@ -0,0 +1,16 @@
use log::debug;
use rouille::Response;
use crate::templates;
pub fn network() -> Response {
debug!("received GET request for: /status/network");
Response::html(templates::status::network())
}
pub fn status() -> Response {
debug!("received GET request for: /status");
Response::html(templates::status::status())
}

View File

@ -0,0 +1,51 @@
use maud::{html, PreEscaped, DOCTYPE};
pub fn base(back: String, title: String, content: PreEscaped<String>) -> PreEscaped<String> {
html! {
(DOCTYPE)
html lang="en" {
head {
meta charset="utf-8";
title { "PeachCloud" }
meta name="description" content="PeachCloud Network";
meta name="author" content="glyph";
meta name="viewport" content="width=device-width, initial-scale=1.0";
link rel="icon" type="image/x-icon" href="/static/icons/peach-icon.png";
link rel="stylesheet" href="/static/css/peachcloud.css";
}
body {
// render the navigation template
(nav(back, title, content))
}
}
}
}
pub fn nav(back: String, title: String, content: PreEscaped<String>) -> PreEscaped<String> {
html! {
(PreEscaped("<!-- TOP NAV BAR -->"))
nav class="nav-bar" {
a class="nav-item" href=(back) title="Back" {
img class="icon-medium nav-icon-left icon-active" src="/static/icons/back.svg" alt="Back";
}
h1 class="nav-title" { (title) }
a class="nav-item" id="logoutButton" href="/logout" title="Logout" {
img class="icon-medium nav-icon-right icon-active" src="/static/icons/enter.svg" alt="Enter";
}
}
(PreEscaped("<!-- MAIN CONTENT CONTAINER -->"))
main { (content) }
(PreEscaped("<!-- BOTTOM NAV BAR -->"))
nav class="nav-bar" {
a class="nav-item" href="https://scuttlebutt.nz/" {
img class="icon-medium nav-icon-left" title="Scuttlebutt Website" src="/static/icons/hermies.png" alt="Secure Scuttlebutt";
}
a class="nav-item" href="/" {
img class="icon nav-icon-left" src="/static/icons/peach-icon.png" alt="PeachCloud" title="Home";
}
a class="nav-item" href="/power" {
img class="icon-medium nav-icon-right icon-active" title="Shutdown" src="/static/icons/power.svg" alt="Power switch";
}
}
}
}

View File

@ -0,0 +1,21 @@
use maud::{html, PreEscaped};
use crate::templates;
pub fn not_found() -> PreEscaped<String> {
let back = "/".to_string();
let title = "404 Not Found".to_string();
let content = html! {
div class="card center" {
div class="capsule-container" {
div class="capsule info-border" {
p { "No PeachCloud resource exists for this URL. Please ensure that the URL in the address bar is correct." }
p { "Click the back arrow in the top-left or the PeachCloud logo at the bottom of your screen to return Home." }
}
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,19 @@
use maud::{html, PreEscaped};
use crate::templates;
// /help
pub fn menu() -> PreEscaped<String> {
let back = "/".to_string();
let title = "Help".to_string();
let content = html! {
div class="card center" {
div class="card-container" {
p { "help content goes here" }
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,77 @@
use maud::{html, PreEscaped};
use crate::context;
use crate::templates;
pub async fn async_test() -> PreEscaped<String> {
let back = "".to_string();
let title = "".to_string();
let whoami = context::test::test_async().await.unwrap();
let content = html! {
p { (whoami) }
};
templates::base::base(back, title, content)
}
pub fn menu() -> PreEscaped<String> {
let back = "".to_string();
let title = "".to_string();
let content = html! {
(PreEscaped("<!-- RADIAL MENU -->"))
div class="grid" {
(PreEscaped("<!-- top-left -->"))
(PreEscaped("<!-- PEERS LINK AND ICON -->"))
a class="top-left" href="/scuttlebutt/peers" title="Scuttlebutt Peers" {
div class="circle circle-small" {
img class="icon-medium" src="/static/icons/users.svg";
}
}
(PreEscaped("<!-- top-middle -->"))
(PreEscaped("<!-- CURRENT USER LINK AND ICON -->"))
a class="top-middle" href="/scuttlebutt/profile" title="Profile" {
div class="circle circle-small" {
img class="icon-medium" src="/static/icons/user.svg";
}
}
(PreEscaped("<!-- top-right -->"))
(PreEscaped("<!-- MESSAGES LINK AND ICON -->"))
a class="top-right" href="/scuttlebutt/private" title="Private Messages" {
div class="circle circle-small" {
img class="icon-medium" src="/static/icons/envelope.svg";
}
}
(PreEscaped("<!-- middle -->"))
a class="middle" href="/hello" {
div class="circle circle-large" { }
}
(PreEscaped("<!-- bottom-left -->"))
(PreEscaped("<!-- SYSTEM STATUS LINK AND ICON -->"))
a class="bottom-left" href="/status" title="Status" {
div class="circle circle-small" {
img class="icon-medium" src="/static/icons/heart-pulse.svg";
}
}
(PreEscaped("<!-- bottom-middle -->"))
(PreEscaped("<!-- PEACHCLOUD GUIDEBOOK LINK AND ICON -->"))
a class="bottom-middle" href="/help" title="Help Menu" {
div class="circle circle-small" {
img class="icon-medium" src="/static/icons/book.svg";
}
}
(PreEscaped("<!-- bottom-right -->"))
(PreEscaped("<!-- SYSTEM SETTINGS LINK AND ICON -->"))
a class="bottom-right" href="/settings" title="Settings Menu" {
div class="circle circle-small" {
img class="icon-medium" src="/static/icons/cog.svg";
}
}
}
};
// we pass the content of this template into the base template
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,30 @@
use maud::{html, PreEscaped};
use crate::templates;
// https://github.com/tomaka/rouille/blob/master/examples/login-session.rs
// /login
pub fn login() -> PreEscaped<String> {
let back = "/".to_string();
let title = "Login".to_string();
let content = html! {
div class="card center" {
div class="card-container" {
form id="login_form" action="/login" method="post" {
// input field for username
input id="username" name="username" class="center input" type="text" placeholder="Username" title="Username for authentication" autofocus { }
// input field for password
input id="password" name="password" class="center input" type="password" placeholder="Password" title="Password for given username" { }
div id="buttonDiv" {
// login button
input id="loginUser" class="button button-primary center" title="Login" type="submit" value="Login" { }
}
}
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,9 @@
pub mod base;
pub mod catchers;
pub mod help;
pub mod home;
pub mod login;
pub mod scuttlebutt;
pub mod settings;
pub mod snippets;
pub mod status;

View File

@ -0,0 +1,109 @@
use maud::{html, PreEscaped};
use crate::templates;
// /scuttlebutt/peers
pub fn peers() -> PreEscaped<String> {
let back = "/".to_string();
let title = "Scuttlebutt Peers".to_string();
let content = html! {
(PreEscaped("<!-- SCUTTLEBUTT PEERS -->"))
div class="card center" {
div class="card-container" {
(PreEscaped("<!-- BUTTONS -->"))
div id="buttons" {
a id="friends" class="button button-primary center" href="/scuttlebutt/friends" title="List Friends" { "Friends" }
a id="follows" class="button button-primary center" href="/scuttlebutt/follows" title="List Follows" { "Follows" }
a id="followers" class="button button-primary center" href="/scuttlebutt/followers" title="List Followers" { "Followers" }
a id="blocks" class="button button-primary center" href="/scuttlebutt/blocks" title="List Blocks" { "Blocks" }
}
}
}
};
templates::base::base(back, title, content)
}
// /scuttlebutt/friends
// /scuttlebutt/follows
// /scuttlebutt/followers
// /scuttlebutt/blocks
pub fn peers_list(title: String) -> PreEscaped<String> {
let back = "/scuttlebutt/peers".to_string();
let content = html! {
(PreEscaped("<!-- SCUTTLEBUTT PEERS LIST -->"))
div class="card center" {
ul class="list" {
// for peer in peers
li {
a class="list-item link light-bg" href="/scuttlebutt/profile/(pub_key)" {
img id="peerImage" class="icon list-icon" src="{ image_path }" alt="{ peer_name }'s profile image";
p id="peerName" class="list-text" { "(name)" }
label class="label-small label-ellipsis list-label font-gray" for="peerName" title="{ peer_name }'s Public Key" { "(public_key)" }
}
}
}
}
};
templates::base::base(back, title, content)
}
// /scuttlebutt/private
pub fn private() -> PreEscaped<String> {
let back = "/".to_string();
let title = "Private Messages".to_string();
let content = html! {
div class="card center" {
div class="card-container" {
p { "private message content goes here" }
}
}
};
templates::base::base(back, title, content)
}
// /scuttlebutt/profile
pub fn profile(flash_msg: Option<String>, flash_name: Option<String>) -> PreEscaped<String> {
let back = "/".to_string();
let title = "Scuttlebutt Profile".to_string();
let content = html! {
(PreEscaped("<!-- USER PROFILE -->"))
div class="card center" {
(PreEscaped("<!-- PROFILE INFO BOX -->"))
div class="capsule capsule-profile" title="Scuttlebutt account profile information" {
(PreEscaped("<!-- edit profile button -->"))
img id="editProfile" class="icon-small nav-icon-right" src="/icons/pencil.svg" alt="Profile picture";
(PreEscaped("<!-- PROFILE BIO -->"))
(PreEscaped("<!-- profile picture -->"))
img id="profilePicture" class="icon-large" src="{ image_path }" alt="Profile picture";
(PreEscaped("<!-- name, public key & description -->"))
p id="profileName" class="card-text" title="Name" { "(name)" }
label class="label-small label-ellipsis font-gray" style="user-select: all;" for="profileName" title="Public Key" { "(public_key)" }
p id="profileDescription" style="margin-top: 1rem" class="card-text" title="Description" { "(description)" }
}
(PreEscaped("<!-- PUBLIC POST FORM -->"))
form id="postForm" action="/scuttlebutt/post" method="post" {
(PreEscaped("<!-- input for message contents -->"))
textarea id="publicPost" class="center input message-input" title="Compose Public Post" { }
input id="publishPost" class="button button-primary center" title="Publish" type="submit" value="Publish";
}
(PreEscaped("<!-- BUTTONS -->"))
(PreEscaped("<!-- TODO: each of these buttons needs to be a form with a public key -->"))
div id="buttons" {
a id="followPeer" class="button button-primary center" href="/scuttlebutt/follow" title="Follow Peer" { "Follow" }
a id="blockPeer" class="button button-warning center" href="/scuttlebutt/block" title="Block Peer" { "Block" }
a id="privateMessage" class="button button-primary center" href="/scuttlebutt/private_message" title="Private Message" { "Private Message" }
}
(PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::snippets::flash_message(flash_msg, flash_name))
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,122 @@
use maud::{html, PreEscaped};
use crate::context::admin;
use crate::templates;
// /settings/admin
pub fn menu() -> PreEscaped<String> {
let back = "/settings".to_string();
let title = "Administrator Settings".to_string();
let content = html! {
(PreEscaped("<!-- ADMIN SETTINGS MENU -->"))
div class="card center" {
(PreEscaped("<!-- BUTTONS -->"))
div id="settingsButtons" {
a id="change" class="button button-primary center" href="/settings/admin/change_password" title="Change Password" { "Change Password" }
a id="configure" class="button button-primary center" href="/settings/admin/configure" title="Configure Admin" { "Configure Admin" }
}
}
};
templates::base::base(back, title, content)
}
// /settings/admin/add
pub fn add(flash_msg: Option<String>, flash_name: Option<String>) -> PreEscaped<String> {
let back = "/settings/admin/configure".to_string();
let title = "Add Administrator".to_string();
let content = html! {
(PreEscaped("<!-- ADD ADMIN FORM -->"))
div class="card center" {
div class="card-container" {
form id="addAdminForm" action="/settings/admin/add" method="post" {
input id="ssb_id" name="ssb_id" class="center input" type="text" placeholder="SSB ID" title="SSB ID of Admin" value="";
div id="buttonDiv" {
input id="addAdmin" class="button button-primary center" title="Add" type="submit" value="Add";
a class="button button-secondary center" href="/settings/admin/configure" title="Cancel" { "Cancel" }
}
}
// render flash message, if any
(templates::snippets::flash_message(flash_msg, flash_name))
}
}
};
templates::base::base(back, title, content)
}
// /settings/admin/configure
pub fn configure(flash_msg: Option<String>, flash_name: Option<String>) -> PreEscaped<String> {
let ssb_admin_ids = admin::ssb_admin_ids();
let back = "/settings/admin".to_string();
let title = "Administrator Configuration".to_string();
let content = html! {
(PreEscaped("<!-- CONFIGURE ADMIN PAGE -->"))
div class="card center" {
div class="text-container" {
h4 { "Current Admins" }
@match ssb_admin_ids {
Ok(admins) => {
@if admins.is_empty() {
div { "No administators are currently configured" }
} else {
@for admin in admins {
div {
form action="/settings/admin/delete" method="post" {
input type="hidden" name="ssb_id" value="{{admin}}";
input type="submit" value="X" title="Delete" {
span { (admin) }
}
}
}
}
}
},
Err(e) => div { "Encountered an error while trying to retrieve list of administrators: " (e) }
}
a class="button button-primary center full-width" style="margin-top: 25px;" href="/settings/admin/add" title="Add Admin" { "Add Admin" }
}
// render flash message, if any
(templates::snippets::flash_message(flash_msg, flash_name))
}
};
templates::base::base(back, title, content)
}
// /settings/admin/change_password
pub fn change_password(
flash_msg: Option<String>,
flash_name: Option<String>,
) -> PreEscaped<String> {
let back = "/settings/admin".to_string();
let title = "Change Password".to_string();
let content = html! {
(PreEscaped("<!-- CHANGE PASSWORD FORM -->"))
div class="card center" {
div class="form-container" {
form id="changePassword" action="/settings/admin/change_password" method="post" {
(PreEscaped("<!-- input for current password -->"))
input id="currentPassword" class="center input" name="current_password" type="password" placeholder="Current password" title="Current password" autofocus;
(PreEscaped("<!-- input for new password -->"))
input id="newPassword" class="center input" name="new_password1" type="password" placeholder="New password" title="New password";
(PreEscaped("<!-- input for duplicate new password -->"))
input id="newPasswordDuplicate" class="center input" name="new_password2" type="password" placeholder="Re-enter new password" title="New password duplicate";
div id="buttonDiv" {
input id="savePassword" class="button button-primary center" title="Add" type="submit" value="Save";
a class="button button-secondary center" href="/settings/admin" title="Cancel" { "Cancel" }
}
}
// render flash message, if any
(templates::snippets::flash_message(flash_msg, flash_name))
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,37 @@
use maud::{html, PreEscaped};
use crate::{templates, STANDALONE_MODE};
// /settings
pub fn menu() -> PreEscaped<String> {
let back = "/".to_string();
let title = "Settings".to_string();
// render a minimal menu (no network settings) if running in standalone mode
let content = if *STANDALONE_MODE {
html! {
(PreEscaped("<!-- SETTINGS MENU -->"))
div class="card center" {
(PreEscaped("<!-- BUTTONS -->"))
div id="settingsButtons" {
a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings" { "Scuttlebutt" }
a id="admin" class="button button-primary center" href="/settings/admin" title="Administrator Settings" { "Administration" }
}
}
}
} else {
html! {
(PreEscaped("<!-- SETTINGS MENU -->"))
div class="card center" {
(PreEscaped("<!-- BUTTONS -->"))
div id="settingsButtons" {
a id="network" class="button button-primary center" href="/settings/network" title="Network Settings" { "Network" }
a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings" { "Scuttlebutt" }
a id="admin" class="button button-primary center" href="/settings/admin" title="Administrator Settings" { "Administration" }
}
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,4 @@
pub mod admin;
pub mod menu;
pub mod network;
pub mod scuttlebutt;

View File

@ -0,0 +1,319 @@
use maud::{html, PreEscaped};
use crate::context::{
network,
network::{ConfigureDNSContext, NetworkDetailContext, NetworkListContext},
};
use crate::templates;
// /settings/network/wifi/add
pub fn add_ap(
ssid: Option<String>,
flash_msg: Option<String>,
flash_name: Option<String>,
) -> PreEscaped<String> {
let back = "/settings/network".to_string();
let title = "Add WiFi Network".to_string();
let content = html! {
(PreEscaped("<!-- NETWORK ADD CREDENTIALS FORM -->"))
div class="card center" {
div class="card-container" {
form id="wifiCreds" action="/settings/network/wifi/add" method="post" {
(PreEscaped("<!-- input for network ssid -->"))
input id="ssid" name="ssid" class="center input" type="text" placeholder="SSID" title="Network name (SSID) for WiFi access point" value=[ssid] autofocus;
(PreEscaped("<!-- input for network password -->"))
input id="pass" name="pass" class="center input" type="password" placeholder="Password" title="Password for WiFi access point";
div id="buttonDiv" {
input id="addWifi" class="button button-primary center" title="Add" type="submit" value="Add";
a class="button button-secondary center" href="/settings/network" title="Cancel" { "Cancel" }
}
}
(PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::snippets::flash_message(flash_msg, flash_name))
}
}
};
templates::base::base(back, title, content)
}
/* TODO: I JUST OVERWROTE THE network_detail FUNCTION :'( :'( :'( */
// /settings/network/wifi
pub fn network_detail(
// the ssid of the network we wish to examine in detail
selected: String,
flash_msg: Option<String>,
flash_name: Option<String>,
) -> PreEscaped<String> {
// retrieve network detail data
let context = NetworkDetailContext::build();
// have credentials for the access point we're viewing previously been saved?
// ie. is this a known access point?
let selected_is_saved = context.saved_aps.contains(&selected);
let back = "/settings/network".to_string();
let title = "WiFi Network Detail".to_string();
let content = html! {
(PreEscaped("<!-- NETWORK DETAIL -->"))
// select only the access point we are interested in
@match context.wlan_networks.get_key_value(&selected) {
Some((ssid, ap)) => {
@let capsule_class = if ssid == &context.wlan_ssid {
"two-grid capsule success-border"
} else {
"two-grid capsule"
};
@let ap_status = if ssid == &context.wlan_ssid {
"CONNECTED"
} else if ap.state == "Available" {
"AVAILABLE"
} else {
"NOT IN RANGE"
};
@let ap_protocol = match &ap.detail {
Some(scan) => scan.protocol.clone(),
None => "Unknown".to_string()
};
@let ap_signal = match ap.signal {
Some(signal) => signal.to_string(),
None => "Unknown".to_string()
};
(PreEscaped("<!-- NETWORK CARD -->"))
div class="card center" {
(PreEscaped("<!-- NETWORK INFO BOX -->"))
div class=(capsule_class) title="PeachCloud network mode and status" {
(PreEscaped("<!-- left column -->"))
(PreEscaped("<!-- NETWORK STATUS ICON -->"))
div class="grid-column-1" {
img id="wifiIcon" class="center icon" src="/icons/wifi.svg" alt="WiFi icon";
label class="center label-small font-gray" for="wifiIcon" title="Access Point Status" { (ap_status) }
}
(PreEscaped("<!-- right column -->"))
(PreEscaped("<!-- NETWORK DETAILED INFO -->"))
div class="grid-column-2" {
label class="label-small font-gray" for="netSsid" title="WiFi network SSID" { "SSID" }
p id="netSsid" class="card-text" title="SSID" { (ssid) }
label class="label-small font-gray" for="netSec" title="Security protocol" { "SECURITY" }
p id="netSec" class="card-text" title="Security protocol in use by "{(ssid)}"" { (ap_protocol) }
label class="label-small font-gray" for="netSig" title="Signal Strength" { "SIGNAL" }
p id="netSig" class="card-text" title="Signal strength of WiFi access point" { (ap_signal) }
}
}
(PreEscaped("<!-- BUTTONS -->"))
div class="card-container" style="padding-top: 0;" {
div id="buttonDiv" {
@if context.wlan_ssid == selected {
form id="wifiDisconnect" action="/settings/network/wifi/disconnect" method="post" {
// hidden element: allows ssid to be sent in request
input id="disconnectSsid" name="ssid" type="text" value=(ssid) style="display: none;";
input id="disconnectWifi" class="button button-warning center" title="Disconnect from Network" type="submit" value="Disconnect";
}
}
// If the selected access point appears in the list,
// display the Modify and Forget buttons.
@if context.saved_aps.contains(&selected) {
@if context.wlan_ssid != selected && ap.state == "Available" {
form id="wifiConnect" action="/settings/network/wifi/connect" method="post" {
// hidden element: allows ssid to be sent in request
input id="connectSsid" name="ssid" type="text" value=(ssid) style="display: none;";
input id="connectWifi" class="button button-primary center" title="Connect to Network" type="submit" value="Connect";
}
}
a class="button button-primary center" href="/settings/network/wifi/modify/"{(ssid)}"" { "Modify" }
form id="wifiForget" action="/settings/network/wifi/forget" method="post" {
// hidden element: allows ssid to be sent in request
input id="forgetSsid" name="ssid" type="text" value=(ssid) style="display: none;";
input id="forgetWifi" class="button button-warning center" title="Forget Network" type="submit" value="Forget";
}
}
@if !selected_is_saved {
// Display the Add button if AP creds not already in saved networks list
a class="button button-primary center" href="/settings/network/wifi/add/"{(ssid)}"" { "Add" };
}
a class="button button-secondary center" href="/settings/network/wifi" title="Cancel" { "Cancel" }
}
(templates::snippets::flash_message(flash_msg, flash_name))
}
}
}
// TODO: improve the styling of this
None => {
div class="card center" {
p { "Selected access point was not found in-range or in the list of saved access points" }
}
}
}
};
templates::base::base(back, title, content)
}
// /settings/network/wifi
pub fn list_aps() -> PreEscaped<String> {
// retrieve network list data
let context = NetworkListContext::build();
let back = "/settings/network".to_string();
let title = "WiFi Networks".to_string();
let content = html! {
(PreEscaped("<!-- NETWORK ACCESS POINT LIST -->"))
div class="card center" {
div class="center list-container" {
ul class="list" {
@if context.ap_state == *"up" {
li class="list-item light-bg warning-border" { "Enable WiFi client mode to view saved and available networks." }
} @else if !context.wlan_networks.is_empty() {
@for (ssid, state) in context.wlan_networks {
li {
@if ssid == context.wlan_ssid {
a class="list-item link primary-bg" href="/settings/network/wifi/ssid/"{(ssid)}"" {
img id="netStatus" class="icon icon-active icon-medium list-icon" src="/static/icons/wifi.svg" alt="WiFi online";
p class="list-text" { (context.wlan_ssid) }
label class="label-small list-label font-gray" for="netStatus" title="Status" { "Connected" }
}
} @else if state == "Available" {
a class="list-item link light-bg" href="/settings/network/wifi/ssid/"{(ssid)}"" {
img id="netStatus" class="icon icon-inactive icon-medium list-icon" src="/static/icons/wifi.svg" alt="WiFi offline";
p class="list-text" { (ssid) }
label class="label-small list-label font-gray" for="netStatus" title="Status" { (state) }
}
} @else {
a class="list-item link" href="/settings/network/wifi/ssid/"{(ssid)}"" {
img id="netStatus" class="icon icon-inactive icon-medium list-icon" src="/static/icons/wifi.svg" alt="WiFi offline";
p class="list-text" { (ssid) }
label class="label-small list-label font-gray" for="netStatus" title="Status" { (state) }
}
}
}
}
}
li class="list-item light-bg" { "No saved or available networks found." }
}
}
}
};
templates::base::base(back, title, content)
}
// /settings/network/wifi
pub fn modify_ap(
ssid: Option<String>,
flash_msg: Option<String>,
flash_name: Option<String>,
) -> PreEscaped<String> {
let back = "/settings/network".to_string();
let title = "Modify WiFi Network".to_string();
let content = html! {
(PreEscaped("<!-- NETWORK MODIFY AP PASSWORD FORM -->"))
div class="card center" {
div class="card-container" {
form id="wifiModify" action="/settings/network/wifi/modify" method="post" {
(PreEscaped("<!-- input for network ssid -->"))
input id="ssid" name="ssid" class="center input" type="text" placeholder="SSID" title="Network name (SSID) for WiFi access point" value=[ssid] autofocus;
(PreEscaped("<!-- input for network password -->"))
input id="pass" name="pass" class="center input" type="password" placeholder="Password" title="Password for WiFi access point";
div id="buttonDiv" {
input id="savePassword" class="button button-primary center" title="Save" type="submit" value="Save";
a class="button button-secondary center" href="/settings/network" title="Cancel" { "Cancel" }
}
}
(templates::snippets::flash_message(flash_msg, flash_name))
}
}
};
templates::base::base(back, title, content)
}
// /settings/network/dns
pub fn configure_dns(flash_msg: Option<String>, flash_name: Option<String>) -> PreEscaped<String> {
// retrieve dyndns-related data
let context = ConfigureDNSContext::build();
let back = "/settings/network".to_string();
let title = "Configure DNS".to_string();
let content = html! {
(PreEscaped("<!-- CONFIGURE DNS FORM -->"))
div class="card center" {
div class="form-container" {
@if context.enable_dyndns {
(PreEscaped("<!-- DYNDNS STATUS INDICATOR -->"))
div id="dyndns-status-indicator" class="stack capsule{% if is_dyndns_online %} success-border{% else %} warning-border{% endif %}" {
div class="stack" {
@if context.is_dyndns_online {
label class="label-small font-near-black" { "Dynamic DNS is currently online." }
} else {
label class="label-small font-near-black" { "Dynamic DNS is enabled but may be offline." }
}
}
}
}
form id="configureDNS" action="/settings/network/dns" method="post" {
div class="input-wrapper" {
(PreEscaped("<!-- input for externaldomain -->"))
label id="external_domain" class="label-small input-label font-near-black" {
label class="label-small input-label font-gray" for="external_domain" style="padding-top: 0.25rem;" { "External Domain (optional)" }
input id="external_domain" class="form-input" style="margin-bottom: 0;" name="external_domain" type="text" title="external domain" value=(context.external_domain);
}
}
div class="input-wrapper" {
div {
(PreEscaped("<!-- checkbox for dynds flag -->"))
label class="label-small input-label font-gray" { "Enable Dynamic DNS" }
input style="margin-left: 0px;" id="enable_dyndns" name="enable_dyndns" title="Activate dyndns" type="checkbox" { @if context.enable_dyndns { "checked" } };
}
}
div class="input-wrapper" {
(PreEscaped("<!-- input for dyndns -->"))
label id="cut" class="label-small input-label font-near-black" {
label class="label-small input-label font-gray" for="cut" style="padding-top: 0.25rem;" { "Dynamic DNS Domain" }
input id="dyndns_domain" class="alert-input" name="dynamic_domain" placeholder="" type="text" title="dyndns_domain" value=(context.dyndns_subdomain) { ".dyn.peachcloud.org" }
}
}
div id="buttonDiv" {
input id="configureDNSButton" class="button button-primary center" title="Add" type="submit" value="Save";
}
}
}
(PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::snippets::flash_message(flash_msg, flash_name))
}
};
templates::base::base(back, title, content)
}
// /settings/network
pub fn menu() -> PreEscaped<String> {
let ap_state = network::ap_state();
let back = "/settings".to_string();
let title = "Network Settings".to_string();
let content = html! {
(PreEscaped("<!-- NETWORK SETTINGS CARD -->"))
div class="card center" {
(PreEscaped("<!-- BUTTONS -->"))
div id="buttons" {
a class="button button-primary center" href="/settings/network/wifi/add" title="Add WiFi Network" { "Add WiFi Network" }
a id="configureDNS" class="button button-primary center" href="/settings/network/dns" title="Configure DNS" { "Configure DNS" }
(PreEscaped("<!-- if ap is up, show \"Enable WiFi\" button, else show \"Deploy Access Point\" -->"))
@if ap_state == *"up" {
a id="connectWifi" class="button button-primary center" href="/settings/network/wifi/activate" title="Enable WiFi" { "Enable WiFi" }
} @else {
a id="deployAccessPoint" class="button button-primary center" href="/settings/network/ap/activate" title="Deploy Access Point" { "Deploy Access Point" }
}
a id="listWifi" class="button button-primary center" href="/settings/network/wifi" title="List WiFi Networks" { "List WiFi Networks" }
a id="viewStatus" class="button button-primary center" href="/status/network" title="View Network Status" { "View Network Status" }
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,29 @@
use maud::{html, PreEscaped};
use crate::templates;
// /settings/scuttlebutt
pub fn scuttlebutt() -> PreEscaped<String> {
let back = "/settings".to_string();
let title = "Scuttlebutt Settings".to_string();
let content = html! {
(PreEscaped("<!-- SCUTTLEBUTT SETTINGS MENU -->"))
div class="card center" {
(PreEscaped("<!-- BUTTONS -->"))
div id="settingsButtons" {
a id="networkKey" class="button button-primary center" href="/settings/scuttlebutt/network_key" title="Set Network Key" { "Set Network Key" }
a id="replicationHops" class="button button-primary center" href="/settings/scuttlebutt/hops" title="Set Replication Hops" { "Set Replication Hops" }
a id="removeFeeds" class="button button-primary center" href="/settings/scuttlebutt/remove_feeds" title="Remove Blocked Feeds" { "Remove Blocked Feeds" }
a id="setDirectory" class="button button-primary center" href="/settings/scuttlebutt/set_directory" title="Set Database Directory" { "Set Database Directory" }
a id="checkFilesystem" class="button button-primary center" href="/settings/scuttlebutt/check_fs" title="Check Filesystem" { "Check Filesystem" }
a id="repairFilesystem" class="button button-primary center" href="/settings/scuttlebutt/repair" title="Repair Filesystem" { "Repair Filesystem" }
a id="disable" class="button button-primary center" href="/settings/scuttlebutt/disable" title="Disable Sbot" { "Disable Sbot" }
a id="enable" class="button button-primary center" href="/settings/scuttlebutt/enable" title="Enable Sbot" { "Enable Sbot" }
a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot" { "Restart Sbot" }
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,22 @@
use maud::{html, PreEscaped};
pub fn flash_message(flash_msg: Option<String>, flash_name: Option<String>) -> PreEscaped<String> {
html! {
(PreEscaped("<!-- FLASH MESSAGE -->"))
@if flash_msg.is_some() {
@if flash_name == Some("success".to_string()) {
div class="capsule center-text flash-message font-success" {
(flash_msg.unwrap())
}
} @else if flash_name == Some("info".to_string()) {
div class="capsule center-text flash-message font-info" {
(flash_msg.unwrap())
}
} @else if flash_name == Some("error".to_string()) {
div class="capsule center-text flash-message font-failure" {
(flash_msg.unwrap())
}
}
}
}
}

View File

@ -0,0 +1,496 @@
use maud::{html, PreEscaped};
use crate::context::{network::NetworkStatusContext, status::StatusContext};
use crate::{templates, STANDALONE_MODE};
fn ap_network_card(status: NetworkStatusContext) -> PreEscaped<String> {
html! {
(PreEscaped("<!-- NETWORK CARD -->"))
div class="card center" {
(PreEscaped("<!-- NETWORK INFO BOX -->"))
div class="capsule capsule-container success-border" {
(PreEscaped("<!-- NETWORK STATUS GRID -->"))
div class="two-grid" title="PeachCloud network mode and status" {
(PreEscaped("<!-- top-right config icon -->"))
a class="link two-grid-top-right" href="/settings/network" title="Configure network settings" {
img id="configureNetworking" class="icon-small" src="/static/icons/cog.svg" alt="Configure";
}
(PreEscaped("<!-- left column -->"))
(PreEscaped("<!-- network mode icon with label -->"))
div class="grid-column-1" {
img id="netModeIcon" class="center icon icon-active" src="/static/icons/router.svg" alt="WiFi router";
label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="Access Point Online" { "ONLINE" }
}
(PreEscaped("<!-- right column -->"))
(PreEscaped("<!-- network mode, ssid & ip with labels -->"))
div class="grid-column-2" {
label class="label-small font-gray" for="netMode" title="Network Mode" { "MODE" }
p id="netMode" class="card-text" title="Network Mode" { "Access Point" }
label class="label-small font-gray" for="netSsid" title="Access Point SSID" { "SSID" }
p id="netSsid" class="card-text" title="SSID" { (status.ap_ssid) }
label class="label-small font-gray" for="netIp" title="Access Point IP Address" { "IP" }
p id="netIp" class="card-text" title="IP" { (status.ap_ip) }
}
}
(PreEscaped("<!-- horizontal dividing line -->"))
hr;
(PreEscaped("<!-- DEVICES AND TRAFFIC GRID -->"))
div class="three-grid card-container" {
// devices stack
div class="stack" {
img id="devices" class="icon icon-medium" title="Connected devices" src="/static/icons/devices.svg" alt="Digital devices";
div class="flex-grid" style="padding-top: 0.5rem;" {
label class="label-medium" for="devices" style="padding-right: 3px;" title="Number of connected devices";
}
label class="label-small font-gray" { "DEVICES" }
}
// download stack
div class="stack" {
img id="dataDownload" class="icon icon-medium" title="Download" src="/static/icons/down-arrow.svg" alt="Download";
div class="flex-grid" style="padding-top: 0.5rem;" {
@if let Some(traffic) = &status.ap_traffic {
label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total in "{ (traffic.rx_unit) }"" { (traffic.rx) }
label class="label-small font-near-black" { (traffic.rx_unit) }
} @else {
label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total";
label class="label-small font-near-black" { "0" }
}
}
label class="label-small font-gray" { "DOWNLOAD" }
}
// upload stack
div class="stack" {
img id="dataUpload" class="icon icon-medium" title="Upload" src="/static/icons/up-arrow.svg" alt="Upload";
div class="flex-grid" style="padding-top: 0.5rem;" {
@if let Some(traffic) = status.ap_traffic {
label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total in "{ (traffic.tx_unit) }"" { (traffic.tx) }
label class="label-small font-near-black" { (traffic.tx_unit) }
} @else {
label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total";
label class="label-small font-near-black" { "0" }
}
}
label class="label-small font-gray" { "UPLOAD" }
}
}
}
}
}
}
fn wlan_network_card(status: NetworkStatusContext) -> PreEscaped<String> {
let capsule = if status.wlan_state == *"up" {
"capsule capsule-container success-border"
} else {
"capsule capsule-container warning-border"
};
html! {
(PreEscaped("<!-- NETWORK CARD -->"))
div class="card center" {
(PreEscaped("<!-- NETWORK INFO BOX -->"))
div class=(capsule) {
@if status.wlan_state == *"up" {
(PreEscaped("<!-- NETWORK STATUS GRID -->"))
div id="netInfoBox" class="two-grid" title="PeachCloud network mode and status" {
a class="link two-grid-top-right" href="/settings/network" title="Configure network settings" {
img id="configureNetworking" class="icon-small" src="/static/icons/cog.svg" alt="Configure";
}
(PreEscaped("<!-- NETWORK STATUS -->"))
(PreEscaped("<!-- left column -->"))
(PreEscaped("<!-- network mode icon with label -->"))
div class="grid-column-1" {
img id="netModeIcon" class="center icon icon-active" src="/static/icons/wifi.svg" alt="WiFi online";
label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="WiFi Client Status" { "ONLINE" }
}
div class="grid-column-2" {
(PreEscaped("<!-- right column -->"))
(PreEscaped("<!-- network mode, ssid & ip with labels -->"))
label class="label-small font-gray" for="netMode" title="Network Mode" { "MODE" }
p id="netMode" class="card-text" title="Network Mode" { "WiFi Client" }
label class="label-small font-gray" for="netSsid" title="WiFi SSID" { "SSID" }
p id="netSsid" class="card-text" title="SSID" { (status.wlan_ssid) }
label class="label-small font-gray" for="netIp" title="WiFi Client IP Address" { "IP" }
p id="netIp" class="card-text" title="IP" { (status.wlan_ip) }
}
}
} @else {
div id="netInfoBox" class="two-grid" title="PeachCloud network mode and status" {
a class="link two-grid-top-right" href="/settings/network" title="Configure network settings" {
img id="configureNetworking" class="icon-small" src="/static/icons/cog.svg" alt="Configure";
}
div class="grid-column-1" {
img id="netModeIcon" class="center icon icon-inactive" src="/static/icons/wifi.svg" alt="WiFi offline";
label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="WiFi Client Status" { "OFFLINE" }
}
div class="grid-column-2" {
(PreEscaped("<!-- right column -->"))
(PreEscaped("<!-- network mode, ssid & ip with labels -->"))
label class="label-small font-gray" for="netMode" title="Network Mode" { "MODE" }
p id="netMode" class="card-text" title="Network Mode" { "WiFi Client" }
label class="label-small font-gray" for="netSsid" title="WiFi SSID" { "SSID" }
p id="netSsid" class="card-text" title="SSID" { (status.wlan_ssid) }
label class="label-small font-gray" for="netIp" title="WiFi Client IP Address" { "IP" }
p id="netIp" class="card-text" title="IP" { (status.wlan_ip) }
}
}
}
(PreEscaped("<!-- horizontal dividing line -->"))
hr;
(PreEscaped("<!-- SIGNAL AND TRAFFIC GRID -->"))
(PreEscaped("<!-- row of icons representing network statistics -->"))
div class="three-grid card-container" {
div class="stack" {
img id="netSignal" class="icon icon-medium" alt="Signal" title="WiFi Signal (%)" src="/static/icons/low-signal.svg";
div class="flex-grid" style="padding-top: 0.5rem;" {
label class="label-medium" for="netSignal" style="padding-right: 3px;" title="Signal strength of WiFi connection (%)" {
@if let Some(wlan_rssi) = status.wlan_rssi { (wlan_rssi) } @else { "0" }
}
}
label class="label-small font-gray" { "SIGNAL" }
}
div class="stack" {
img id="dataDownload" class="icon icon-medium" alt="Download" title="WiFi download total" src="/static/icons/down-arrow.svg";
div class="flex-grid" style="padding-top: 0.5rem;" {
@if let Some(traffic) = &status.wlan_traffic {
(PreEscaped("<!-- display wlan traffic data -->"))
label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total in "{ (traffic.rx_unit) }"" { (traffic.rx) }
label class="label-small font-near-black" { (traffic.rx_unit) }
} @else {
(PreEscaped("<!-- no wlan traffic data to display -->"))
label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total" { "0" }
label class="label-small font-near-black" { "MB" }
}
}
label class="label-small font-gray" { "DOWNLOAD" }
}
div class="stack" {
img id="dataUpload" class="icon icon-medium" alt="Upload" title="WiFi upload total" src="/static/icons/up-arrow.svg";
div class="flex-grid" style="padding-top: 0.5rem;" {
@if let Some(traffic) = status.wlan_traffic {
(PreEscaped("<!-- display wlan traffic data -->"))
label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total in "{ (traffic.tx_unit) }"" { (traffic.tx) }
label class="label-small font-near-black" { (traffic.tx_unit) }
} @else {
(PreEscaped("<!-- no wlan traffic data to display -->"))
label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total" { "0" }
label class="label-small font-near-black" { "MB" }
}
}
label class="label-small font-gray" { "UPLOAD" }
}
}
}
}
}
}
/*
* WORKS ... kinda
fn wlan_network_card(status: NetworkStatusContext) -> PreEscaped<String> {
html! {
(PreEscaped("<!-- NETWORK CARD -->"))
div class="card center" {
(PreEscaped("<!-- NETWORK INFO BOX -->"))
@if status.wlan_state == *"up" {
div class="capsule capsule-container success-border" {
(PreEscaped("<!-- NETWORK STATUS GRID -->"))
div id="netInfoBox" class="two-grid" title="PeachCloud network mode and status" {
a class="link two-grid-top-right" href="/settings/network" title="Configure network settings" {
img id="configureNetworking" class="icon-small" src="/static/icons/cog.svg" alt="Configure";
}
(PreEscaped("<!-- NETWORK STATUS -->"))
(PreEscaped("<!-- left column -->"))
(PreEscaped("<!-- network mode icon with label -->"))
div class="grid-column-1" {
img id="netModeIcon" class="center icon icon-active" src="/static/icons/wifi.svg" alt="WiFi online";
label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="WiFi Client Status" { "ONLINE" }
}
div class="grid-column-2" {
(PreEscaped("<!-- right column -->"))
(PreEscaped("<!-- network mode, ssid & ip with labels -->"))
label class="label-small font-gray" for="netMode" title="Network Mode" { "MODE" }
p id="netMode" class="card-text" title="Network Mode" { "WiFi Client" }
label class="label-small font-gray" for="netSsid" title="WiFi SSID" { "SSID" }
p id="netSsid" class="card-text" title="SSID" { (status.wlan_ssid) }
label class="label-small font-gray" for="netIp" title="WiFi Client IP Address" { "IP" }
p id="netIp" class="card-text" title="IP" { (status.wlan_ip) }
}
}
}
} @else {
div class="capsule capsule-container warning-border" {
div id="netInfoBox" class="two-grid" title="PeachCloud network mode and status" {
a class="link two-grid-top-right" href="/settings/network" title="Configure network settings" {
img id="configureNetworking" class="icon-small" src="/static/icons/cog.svg" alt="Configure";
}
div class="grid-column-1" {
img id="netModeIcon" class="center icon icon-inactive" src="/static/icons/wifi.svg" alt="WiFi offline";
label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="WiFi Client Status" { "OFFLINE" }
}
div class="grid-column-2" {
(PreEscaped("<!-- right column -->"))
(PreEscaped("<!-- network mode, ssid & ip with labels -->"))
label class="label-small font-gray" for="netMode" title="Network Mode" { "MODE" }
p id="netMode" class="card-text" title="Network Mode" { "WiFi Client" }
label class="label-small font-gray" for="netSsid" title="WiFi SSID" { "SSID" }
p id="netSsid" class="card-text" title="SSID" { (status.wlan_ssid) }
label class="label-small font-gray" for="netIp" title="WiFi Client IP Address" { "IP" }
p id="netIp" class="card-text" title="IP" { (status.wlan_ip) }
}
}
}
}
(PreEscaped("<!-- horizontal dividing line -->"))
hr;
(PreEscaped("<!-- SIGNAL AND TRAFFIC GRID -->"))
(PreEscaped("<!-- row of icons representing network statistics -->"))
div class="three-grid card-container" {
div class="stack" {
img id="netSignal" class="icon icon-medium" alt="Signal" title="WiFi Signal (%)" src="/static/icons/low-signal.svg";
div class="flex-grid" style="padding-top: 0.5rem;" {
label class="label-medium" for="netSignal" style="padding-right: 3px;" title="Signal strength of WiFi connection (%)" {
@if let Some(wlan_rssi) = status.wlan_rssi { (wlan_rssi) } @else { "0" }
}
}
label class="label-small font-gray" { "SIGNAL" }
}
div class="stack" {
img id="dataDownload" class="icon icon-medium" alt="Download" title="WiFi download total" src="/static/icons/down-arrow.svg";
div class="flex-grid" style="padding-top: 0.5rem;" {
@if let Some(traffic) = &status.wlan_traffic {
(PreEscaped("<!-- display wlan traffic data -->"))
label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total in "{ (traffic.rx_unit) }"" { (traffic.rx) }
label class="label-small font-near-black" { (traffic.rx_unit) }
} @else {
(PreEscaped("<!-- no wlan traffic data to display -->"))
label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total" { "0" }
label class="label-small font-near-black" { "MB" }
}
}
label class="label-small font-gray" { "DOWNLOAD" }
}
div class="stack" {
img id="dataUpload" class="icon icon-medium" alt="Upload" title="WiFi upload total" src="/static/icons/up-arrow.svg";
div class="flex-grid" style="padding-top: 0.5rem;" {
@if let Some(traffic) = status.wlan_traffic {
(PreEscaped("<!-- display wlan traffic data -->"))
label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total in "{ (traffic.tx_unit) }"" { (traffic.tx) }
label class="label-small font-near-black" { (traffic.tx_unit) }
} @else {
(PreEscaped("<!-- no wlan traffic data to display -->"))
label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total" { "0" }
label class="label-small font-near-black" { "MB" }
}
}
label class="label-small font-gray" { "UPLOAD" }
}
}
}
}
}
*/
pub fn network() -> PreEscaped<String> {
let back = "/status".to_string();
let title = "Network Status".to_string();
// retrieve network status data
let status = NetworkStatusContext::build();
let content = html! {
(PreEscaped("<!-- NETWORK STATUS -->"))
// if ap is up, show ap card, else show wlan card
@if status.ap_state == *"up" {
(ap_network_card(status))
} @else {
(wlan_network_card(status))
}
};
templates::base::base(back, title, content)
}
fn scuttlebutt_status() -> PreEscaped<String> {
html! {
(PreEscaped("<!-- SCUTTLEBUTT STATUS -->"))
div class="card center" {
div class="card-container" {
p { "Network key: " }
p { "Replication hops: " }
p { "Sbot version: " }
p { "Process status: " }
p { "Process uptime: " }
p { "Blobstore size: " }
p { "Latest sequence number: " }
p { "Last time you visited this page, latest sequence was x ... now it's y" }
p { "Number of follows / followers / friends / blocks" }
}
}
}
}
pub fn status() -> PreEscaped<String> {
let back = "/".to_string();
let title = if *STANDALONE_MODE {
"Scuttlebutt Status".to_string()
} else {
"System Status".to_string()
};
// retrieve system status data
let status = StatusContext::build();
// render the scuttlebutt status template
let content = if *STANDALONE_MODE {
scuttlebutt_status()
// or render the complete system status template
} else {
html! {
(PreEscaped("<!-- SYSTEM STATUS -->"))
div class="card center" {
div class="card-container" {
div class="three-grid" {
(PreEscaped("<!-- PEACH-NETWORK STATUS STACK -->"))
(PreEscaped("<!-- Display microservice status for network, oled & stats -->"))
a class="link" href="/status/network" {
div class="stack capsule success-border" {
img id="networkIcon" class="icon icon-medium" alt="Network" title="Network microservice status" src="/static/icons/wifi.svg";
div class="stack" style="padding-top: 0.5rem;" {
label class="label-small font-near-black" { "Networking" };
label class="label-small font-near-black" { "(network_ping)" };
}
}
}
(PreEscaped("<!-- PEACH-OLED STATUS STACK -->"))
div class="stack capsule success-border" {
img id="oledIcon" class="icon icon-medium" alt="Display" title="OLED display microservice status" src="/static/icons/lcd.svg";
div class="stack" style="padding-top: 0.5rem;" {
label class="label-small font-near-black" { "Display" };
label class="label-small font-near-black" { "(oled_ping)" };
}
}
(PreEscaped("<!-- PEACH-STATS STATUS STACK -->"))
div class="stack capsule success-border" {
img id="statsIcon" class="icon icon-medium" alt="Stats" title="System statistics microservice status" src="/static/icons/chart.svg";
div class="stack" style="padding-top: 0.5rem;" {
label class="label-small font-near-black" { "Statistics" };
label class="label-small font-near-black" { "AVAILABLE" };
}
}
(PreEscaped("<!-- DYNDNS STATUS STACK -->"))
(PreEscaped("<!-- Display status for dynsdns, config & sbot -->"))
div class="stack capsule success-border" {
img id="networkIcon" class="icon icon-medium" alt="Dyndns" title="Dyndns status" src="/static/icons/dns.png";
div class="stack" style="padding-top: 0.5rem;" {
label class="label-small font-near-black" { "Dyn DNS" };
label class="label-small font-near-black" { "(dns_ping)" };
}
}
(PreEscaped("<!-- CONFIG STATUS STACK -->"))
// TODO: render capsule border according to status
div class="stack capsule success-border" {
img id="networkIcon" class="icon icon-medium" alt="Config" title="Config status" src="/static/icons/clipboard.png";
div class="stack" style="padding-top: 0.5rem;" {
label class="label-small font-near-black" { "Config" };
label class="label-small font-near-black" { "(status)" };
}
}
(PreEscaped("<!-- SBOT STATUS STACK -->"))
div class="stack capsule success-border" {
img id="networkIcon" class="icon icon-medium" alt="Sbot" title="Sbot status" src="/static/icons/hermies.svg";
div class="stack" style="padding-top: 0.5rem;" {
label class="label-small font-near-black" { "Sbot" };
label class="label-small font-near-black" { "(sbot_status)" }
}
}
}
}
div class="card-container" {
(PreEscaped("<!-- Display CPU usage meter -->"))
@match status.cpu_usage_percent {
Some(x) => {
div class="flex-grid" {
span class="card-text" { "CPU" }
span class="label-small push-right" { (x) "%" }
}
meter value=(x) min="0" max="100" title="CPU usage" {
div class="meter-gauge" {
span style="width: "{(x)}"%;" { "CPU Usage" }
}
}
},
_ => p class="card-text" { "CPU usage data unavailable" }
}
(PreEscaped("<!-- Display memory usage meter -->"))
@match status.mem_usage_percent {
Some(x) => {
@let (mem_free, mem_unit) = match status.mem_free {
Some(x) => {
if x > 1024 {
((x / 1024), "GB".to_string())
} else {
(x, "MB".to_string())
}
},
_ => (0, "MB".to_string())
};
@let mem_total = status.mem_total.unwrap_or(0);
@let mem_used = status.mem_used.unwrap_or(0);
div class="flex-grid" {
span class="card-text" { "Memory" }
span class="label-small push-right" { (x) " % ("(mem_free)" "(mem_unit)" free)" }
}
meter value=(mem_used) min="0" max=(mem_total) title="Memory usage" {
div class="meter-gauge" {
span style="width: "{(x)}"%;" { "Memory Usage" }
}
}
},
_ => p class="card-text" { "Memory usage data unavailable" }
}
(PreEscaped("<!-- Display disk usage meter -->"))
@match status.disk_usage_percent {
Some(x) => {
// define free disk space with appropriate unit (GB if > 1024)
@let (disk_free, disk_unit) = match status.disk_free {
Some(x) => {
if x > 1024 {
((x / 1024), "GB".to_string())
} else {
(x, "MB".to_string())
}
},
_ => (0, "MB".to_string())
};
div class="flex-grid" {
span class="card-text" { "Disk" };
span class="label-small push-right" { (x) " % ("(disk_free)" "(disk_unit)")" };
}
meter value=(x) min="0" max="100" title="Disk usage" {
div class="meter-gauge" {
span style="width: "{(x)}"%;" { "Disk Usage" };
}
}
},
_ => p class="card-text" { "Disk usage data unavailable" }
}
(PreEscaped("<!-- Display system uptime in minutes -->"))
@match status.uptime {
Some(x) => {
@if x < 60 {
p class="capsule center-text" { "Uptime: "(x)" minutes" }
} @else {
@let hours = x / 60;
@let mins = x % 60;
p class="capsule center-text" { "Uptime: "(hours)" hours, "(mins)" minutes" }
}
},
_ => p class="card-text" { "Uptime data unavailable" }
}
}
}
}
};
templates::base::base(back, title, content)
}

View File

@ -0,0 +1,177 @@
/*
VARIABLES
*/
@custom-media --breakpoint-not-small screen and (min-width: 30em);
@custom-media --breakpoint-medium screen and (min-width: 30em) and (max-width: 60em);
@custom-media --breakpoint-large screen and (min-width: 60em);
:root {
--sans-serif: -apple-system, BlinkMacSystemFont, 'avenir next', avenir, helvetica, 'helvetica neue', ubuntu, roboto, noto, 'segoe ui', arial, sans-serif;
--serif: georgia, serif;
--code: consolas, monaco, monospace;
--font-size-headline: 6rem;
--font-size-subheadline: 5rem;
--font-size-1: 3rem;
--font-size-2: 2.25rem;
--font-size-3: 1.5rem;
--font-size-4: 1.25rem;
--font-size-5: 1rem;
--font-size-6: .875rem;
--font-size-7: .75rem;
--letter-spacing-tight:-.05em;
--letter-spacing-1:.1em;
--letter-spacing-2:.25em;
--line-height-solid: 1;
--line-height-title: 1.25;
--line-height-copy: 1.5;
--measure: 30em;
--measure-narrow: 20em;
--measure-wide: 34em;
--spacing-none: 0;
--spacing-extra-small: .25rem;
--spacing-small: .5rem;
--spacing-medium: 1rem;
--spacing-large: 2rem;
--spacing-extra-large: 4rem;
--spacing-extra-extra-large: 8rem;
--spacing-extra-extra-extra-large: 16rem;
--spacing-copy-separator: 1.5em;
--height-1: 1rem;
--height-2: 2rem;
--height-3: 4rem;
--height-4: 8rem;
--height-5: 16rem;
--width-1: 1rem;
--width-2: 2rem;
--width-3: 4rem;
--width-4: 8rem;
--width-5: 16rem;
--max-width-1: 1rem;
--max-width-2: 2rem;
--max-width-3: 4rem;
--max-width-4: 8rem;
--max-width-5: 16rem;
--max-width-6: 32rem;
--max-width-7: 48rem;
--max-width-8: 64rem;
--max-width-9: 96rem;
--border-radius-none: 0;
--border-radius-1: .125rem;
--border-radius-2: .25rem;
--border-radius-3: .5rem;
--border-radius-4: 1rem;
--border-radius-circle: 100%;
--border-radius-pill: 9999px;
--border-width-none: 0;
--border-width-1: .125rem;
--border-width-2: .25rem;
--border-width-3: .5rem;
--border-width-4: 1rem;
--border-width-5: 2rem;
--box-shadow-1: 0px 0px 4px 2px rgba( 0, 0, 0, 0.2 );
--box-shadow-2: 0px 0px 8px 2px rgba( 0, 0, 0, 0.2 );
--box-shadow-3: 2px 2px 4px 2px rgba( 0, 0, 0, 0.2 );
--box-shadow-4: 2px 2px 8px 0px rgba( 0, 0, 0, 0.2 );
--box-shadow-5: 4px 4px 8px 0px rgba( 0, 0, 0, 0.2 );
--black: #000;
--near-black: #111;
--dark-gray:#333;
--mid-gray:#555;
--gray: #777;
--silver: #999;
--light-silver: #aaa;
--moon-gray: #ccc;
--light-gray: #eee;
--near-white: #f4f4f4;
--white: #fff;
--transparent: transparent;
--black-90: rgba(0,0,0,.9);
--black-80: rgba(0,0,0,.8);
--black-70: rgba(0,0,0,.7);
--black-60: rgba(0,0,0,.6);
--black-50: rgba(0,0,0,.5);
--black-40: rgba(0,0,0,.4);
--black-30: rgba(0,0,0,.3);
--black-20: rgba(0,0,0,.2);
--black-10: rgba(0,0,0,.1);
--black-05: rgba(0,0,0,.05);
--black-025: rgba(0,0,0,.025);
--black-0125: rgba(0,0,0,.0125);
--white-90: rgba(255,255,255,.9);
--white-80: rgba(255,255,255,.8);
--white-70: rgba(255,255,255,.7);
--white-60: rgba(255,255,255,.6);
--white-50: rgba(255,255,255,.5);
--white-40: rgba(255,255,255,.4);
--white-30: rgba(255,255,255,.3);
--white-20: rgba(255,255,255,.2);
--white-10: rgba(255,255,255,.1);
--white-05: rgba(255,255,255,.05);
--white-025: rgba(255,255,255,.025);
--white-0125: rgba(255,255,255,.0125);
--dark-red: #e7040f;
--red: #ff4136;
--light-red: #ff725c;
--orange: #ff6300;
--gold: #ffb700;
--yellow: #ffd700;
--light-yellow: #fbf1a9;
--purple: #5e2ca5;
--light-purple: #a463f2;
--dark-pink: #d5008f;
--hot-pink: #ff41b4;
--pink: #ff80cc;
--light-pink: #ffa3d7;
--dark-green: #137752;
--green: #19a974;
--light-green: #9eebcf;
--navy: #001b44;
--dark-blue: #00449e;
--blue: #357edd;
--light-blue: #96ccff;
--lightest-blue: #cdecff;
--washed-blue: #f6fffe;
--washed-green: #e8fdf5;
--washed-yellow: #fffceb;
--washed-red: #ffdfdf;
/* PEACHCLOUD-SPECIFIC VARIABLES */
--primary: var(--light-green);
--secondary: var(--near-white);
--success: var(--green);
--info: var(--blue);
--warning: var(--orange);
--danger: var(--red);
--light: var(--light-gray);
--dark: var(--near-black);
/* we need to add shades for each accent colour
*
* --info-100
* --info-200
* --info-300 -> var(--blue)
* --info-400
* --info-500
*/
}

View File

@ -0,0 +1,971 @@
@import url('/static/css/_variables.css');
/* ------------------------------ *\
* peachcloud.css
*
* Index
* - ALIGNMENT
* - BODY
* - BUTTONS
* - CARDS
* - CAPSULES
* - CIRCLES
* - COLORS
* - GRIDS
* - HTML
* - FLASH MESSAGE
* - FONTS
* - ICONS
* - INPUTS
* - LABELS
* - LINKS
* - LISTS
* - MAIN
* - METERS
* - NAVIGATION
* - PARAGRAPHS
* - SWITCHES / SLIDERS
*
\* ------------------------------ */
/*
* ALIGNMENT
*/
.center {
display: block;
margin-left: auto;
margin-right: auto;
}
.center-text {
text-align: center;
}
.center-vert {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.push-right {
margin-left: auto;
padding-right: 0;
}
.top-left {
/* place-self combines align-self and justify-self */
place-self: end center;
}
@media only screen and (min-width: 600px) {
.top-left {
place-self: end;
}
}
.top-right {
place-self: end center;
}
@media only screen and (min-width: 600px) {
.top-right {
place-self: end start;
}
}
.top-middle {
place-self: center;
}
@media only screen and (min-width: 600px) {
.top-middle {
padding-bottom: 2rem;
place-self: center;
}
}
.middle {
place-self: center;
grid-column-start: 1;
grid-column-end: 4;
}
.bottom-middle {
place-self: center;
}
@media only screen and (min-width: 600px) {
.bottom-middle {
padding-top: 2rem;
place-self: center;
}
}
.bottom-left {
place-self: start center;
}
@media only screen and (min-width: 600px) {
.bottom-left {
place-self: start end;
}
}
.bottom-right {
place-self: start center;
}
@media only screen and (min-width: 600px) {
.bottom-right {
place-self: start;
}
}
/*
* BODY
*/
body {
background-color: var(--moon-gray);
height: 100%;
display: flex;
flex-direction: column;
margin: 0;
}
/*
* BUTTONS
*/
.button {
border: 1px solid var(--near-black);
border-radius: var(--border-radius-2);
/* Needed to render inputs & buttons of equal width */
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: var(--near-black);
cursor: pointer;
padding: 10px;
text-align: center;
text-decoration: none;
font-size: var(--font-size-5);
font-family: var(--sans-serif);
width: 80%;
margin-top: 5px;
margin-bottom: 5px;
}
.button.full-width {
width: 100%;
}
.button-div {
grid-column-start: 1;
grid-column-end: 4;
margin-bottom: 1rem;
}
.button-primary {
background-color: var(--light-gray);
}
.button-primary:hover {
background-color: var(--primary);
}
.button-primary:focus {
background-color: var(--primary);
outline: none;
}
.button-secondary {
background-color: var(--light-gray);
}
.button-secondary:hover {
background-color: var(--light-silver);
}
.button-secondary:focus {
background-color: var(--light-silver);
outline: none;
}
.button-warning {
background-color: var(--light-gray);
}
.button-warning:hover {
background-color: var(--light-red);
}
.button-warning:focus {
background-color: var(--light-red);
outline: none;
}
/*
* CAPSULES
*/
.capsule {
padding: 1rem;
border: var(--border-width-1) solid;
border-radius: var(--border-radius-3);
background-color: var(--light-gray);
/* margin-top: 1rem; */
/* margin-bottom: 1rem; */
}
.capsule-container {
margin-left: 1rem;
margin-right: 1rem;
padding-bottom: 1rem;
}
@media only screen and (min-width: 600px) {
.capsule-container {
margin-left: 0;
margin-right: 0;
}
}
/*
* CARDS
*/
.card {
min-height: 50vh;
max-height: 90vh;
position: relative;
width: 100%;
margin-top: 1rem;
}
@media only screen and (min-width: 600px) {
.card {
min-height: 50vh;
max-height: 90vh;
width: 20rem;
}
}
.card-container {
justify-content: center;
padding: 0.5rem;
}
.form-container {
justify-content: center;
padding-top: 1rem;
padding-bottom: 1rem;
width: 80%;
margin: auto;
}
.text-container {
width: 80%;
margin: auto;
}
.card-text {
margin: 0;
font-size: var(--font-size-5);
padding-bottom: 0.3rem;
}
.container {
display: grid;
grid-template-columns: 2fr 5fr 2fr;
grid-template-rows: auto;
grid-row-gap: 1rem;
align-items: center;
justify-items: center;
margin-bottom: 1rem;
margin-top: 1rem;
}
/*
* CIRCLES
*/
.circle {
align-items: center;
background: var(--light-gray);
border-radius: 50%;
box-shadow: var(--box-shadow-3);
display: flex;
justify-content: center;
position: relative;
}
.circle-small {
height: 5rem;
width: 5rem;
}
.circle-medium {
height: 8rem;
width: 8rem;
}
.circle-large {
height: 13rem;
width: 13rem;
}
.circle-success {
background-color: var(--success);
color: var(--white);
font-size: var(--font-size-4);
}
.circle-warning {
background-color: var(--warning);
color: var(--white);
font-size: var(--font-size-4);
}
.circle-error {
background-color: var(--danger);
color: var(--white);
font-size: var(--font-size-4);
}
/* quartered-circle: circle for the center of radial-menu */
.quartered-circle {
width: 100px;
height: 100px;
}
.quarter {
width: 50%;
height: 50%;
}
.quarter-link {
left: 50%;
margin: -2em;
top: 50%;
}
.quarter-icon {
position: absolute;
bottom: 1em;
left: 1.5em;
}
/*
* COLORS
*/
.primary-bg {
background-color: var(--primary);
}
.secondary-bg {
background-color: var(--secondary);
}
.success-bg {
background-color: var(--success);
}
.info-bg {
background-color: var(--info);
}
.warning-bg {
background-color: var(--warning);
}
.danger-bg {
background-color: var(--danger);
}
.light-bg {
background-color: var(--light);
}
.primary-border {
border-color: var(--primary);
}
.success-border {
border-color: var(--success);
}
.info-border {
border-color: var(--info);
}
.warning-border {
border-color: var(--warning);
}
.danger-border {
border-color: var(--danger);
}
.dark-gray-border {
border-color: var(--dark-gray);
}
/*
* GRIDS
*/
.grid {
display: grid;
grid-template-columns: 2fr 1fr 2fr;
grid-template-rows: 2fr 1fr 2fr;
height: 80vh;
}
.flex-grid {
display: flex;
align-content: space-between;
align-items: baseline;
justify-content: center;
}
.two-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto;
align-items: center;
justify-content: center;
justify-items: center;
padding-bottom: 1rem;
/* margin-right: 2rem; */
/* margin-left: 2rem; */
/* padding-top: 1.5rem; */
}
.two-grid-top-right {
grid-column: 2;
justify-self: right;
padding: 0;
}
.three-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto;
grid-gap: 10px;
align-items: center;
justify-content: center;
}
.profile-grid {
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: auto;
grid-gap: 10px;
align-items: center;
justify-content: center;
justify-items: center;
margin-right: 2rem;
margin-left: 2rem;
padding-top: 1.5rem;
padding-bottom: 1rem;
}
.stack {
display: grid;
align-items: flex-end;
justify-items: center;
justify-content: center;
}
.three-grid-icon-1 {
align-self: center;
grid-column: 1;
grid-row: 1;
justify-self: center;
margin-bottom: 10px;
max-width: 55%;
text-align: center;
}
.three-grid-icon-2 {
align-self: center;
grid-column: 2;
grid-row: 1;
justify-self: center;
margin-bottom: 10px;
max-width: 55%;
text-align: center;
}
.three-grid-icon-3 {
align-self: center;
grid-column: 3;
grid-row: 1;
justify-self: center;
margin-bottom: 10px;
max-width: 55%;
text-align: center;
}
.three-grid-label-1 {
align-self: center;
grid-column: 1;
grid-row: 1;
justify-self: center;
text-align: center;
}
.three-grid-label-2 {
align-self: center;
grid-column: 2;
grid-row: 1;
justify-self: center;
text-align: center;
}
.three-grid-label-3 {
align-self: center;
grid-column: 3;
grid-row: 1;
justify-self: center;
text-align: center;
}
.grid-column-1 {
grid-column: 1;
}
.grid-column-2 {
grid-column: 2;
justify-self: left;
}
.grid-column-3 {
grid-column: 3;
}
/*
* HTML
*/
html {
height: 100%;
}
/*
* FLASH MESSAGE
*/
.flash-message {
font-family: var(--sans-serif);
font-size: var(--font-size-6);
margin-left: 2rem;
margin-right: 2rem;
margin-top: 1rem;
}
/*
* FONTS
*/
.font-near-black {
color: var(--near-black);
}
.font-gray {
color: var(--mid-gray);
}
.font-light-gray {
color: var(--silver);
}
.font-success {
color: var(--success);
}
.font-warning {
color: var(--warning);
}
.font-failure {
color: var(--danger);
}
/*
* ICONS
*/
.icon {
width: 3rem;
}
.icon-small {
width: 1rem;
}
.icon-medium {
width: 2rem;
}
.icon-large {
width: 5rem;
}
.icon-100 {
width: 100%;
}
/* icon-active: sets color of icon svg to near-black */
.icon-active {
filter: invert(0%) sepia(1%) saturate(4171%) hue-rotate(79deg) brightness(86%) contrast(87%);
}
/* icon-inactive: sets color of icon svg to gray */
.icon-inactive {
filter: invert(72%) sepia(8%) saturate(14%) hue-rotate(316deg) brightness(93%) contrast(92%);
}
/*
* INPUTS
*/
.input {
/* Needed to render inputs & buttons of equal width */
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin-top: 0.5rem;
margin-bottom: 1rem;
padding-left: 5px;
line-height: 1.5rem;
width: 80%;
}
.form-input {
margin-bottom: 0;
margin-left: 0px;
border: 0px;
padding-left: 5px;
line-height: 1.5rem;
width: 100%;
}
.message-input {
height: 7rem;
overflow: auto;
resize: vertical;
}
.alert-input {
/* Needed to render inputs & buttons of equal width */
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin-right: 0.25rem;
padding-right: 0.25rem;
text-align: right;
width: 7rem;
}
.input-wrapper {
margin-bottom: 15px;
}
/*
* LABELS
*/
.label-small {
font-family: var(--sans-serif);
font-size: var(--font-size-7);
display: block;
}
.label-medium {
font-size: var(--font-size-3);
display: block;
}
.label-large {
font-size: var(--font-size-2);
display: block;
}
.label-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
width: 10rem;
}
.input-label {
margin-bottom: 0.4rem;
}
/*
* LINKS
*/
.link {
text-decoration: none;
color: var(--font-near-black);
}
/*
* LISTS
*/
.list {
padding-left: 0;
margin-left: 0;
max-width: var(--max-width-6);
border: 1px solid var(--light-silver);
border-radius: var(--border-radius-2);
list-style-type: none;
font-family: var(--sans-serif);
}
.list-container {
width: var(--max-width-5);
}
.list-icon {
align-self: center;
justify-self: right;
grid-column: 2;
grid-row: 1/3;
}
.list-item {
display: grid;
padding: 1rem;
border-bottom-color: var(--light-silver);
border-bottom-style: solid;
border-bottom-width: 1px;
}
.list-text {
justify-self: left;
grid-column: 1;
grid-row: 1;
margin: 0;
font-size: var(--font-size-5);
}
.list-label {
justify-self: left;
grid-column: 1;
grid-row: 2;
}
/*
* MAIN
*/
main {
flex: 1 0 auto;
}
/*
* METERS
*/
meter {
border: 1px solid #ccc;
border-radius: 3px;
display: block;
/* height: 1rem; */
margin: 0 auto;
margin-bottom: 1rem;
width: 100%;
/* remove default styling */
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
/* Firefox */
background: none; /* remove default background */
background-color: var(--near-white);
box-shadow: 0 5px 5px -5px #333 inset;
}
meter::-webkit-meter-bar {
background: none; /* remove default background */
background-color: var(--near-white);
box-shadow: 0 5px 5px -5px #333 inset;
}
meter::-webkit-meter-optimum-value {
background-size: 100% 100%;
box-shadow: 0 5px 5px -5px #999 inset;
transition: width .5s;
}
/* Firefox styling */
meter::-moz-meter-bar {
background: var(--mid-gray);
background-size: 100% 100%;
box-shadow: 0 5px 5px -5px #999 inset;
}
.meter-gauge {
background-color: var(--near-white);
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 0 5px 5px -5px #333 inset;
display: block;
}
/* Chrome styling */
.meter-gauge > span {
background: var(--mid-gray);
background-size: 100% 100%;
box-shadow: 0 5px 5px -5px #999 inset;
display: block;
height: inherit;
text-indent: -9999px;
}
/*
* NAVIGATION
*/
.nav-bar {
display: flex;
align-items: center;
width: 100%;
height: 2em;
padding-top: 1rem;
padding-bottom: 1rem;
justify-content: space-between;
}
.nav-title {
font-family: var(--sans-serif);
font-size: var(--font-size-4);
font-weight: normal;
margin: 0;
}
.nav-icon {
width: auto;
height: 90%;
cursor: pointer;
}
.nav-icon-left {
float: left;
padding-left: 10px;
}
.nav-icon-right {
float: right;
padding-right: 10px;
}
.nav-item {
display: inline-block;
list-style-type: none;
}
/*
* PARAGRAPHS
*/
p {
font-family: var(--sans-serif);
overflow-wrap: anywhere;
}
/*
* SWITCHES / SLIDERS
*/
/* switch: the box around the slider */
.switch {
display: inline-block;
height: 34px;
position: relative;
width: 60px;
}
/* hide default HTML checkbox */
.switch input {
height: 0;
opacity: 0;
width: 0;
}
.switch-icon-left {
align-self: center;
grid-column: 1;
grid-row: 1;
justify-self: center;
}
.switch-icon-right {
align-self: center;
grid-column: 3;
grid-row: 1;
justify-self: center;
}
.slider {
background-color: var(--moon-gray);
bottom: 0;
cursor: pointer;
left: 0;
position: absolute;
right: 0;
top: 0;
transition: .4s;
-webkit-transition: .4s;
}
.slider:before {
background-color: var(--white);
bottom: 4px;
content: "";
height: 26px;
left: 4px;
position: absolute;
transition: .4s;
-webkit-transition: .4s;
width: 26px;
}
input:checked + .slider {
background-color: var(--near-black);
}
input:focus + .slider {
box-shadow: 0 0 1px var(--near-black);
}
input:checked + .slider:before {
-ms-transform: translateX(26px);
transform: translateX(26px);
-webkit-transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
/*
* TITLES
*/
.title-medium {
font-size: var(--font-size-4);
font-family: var(--sans-serif);
max-width: var(--max-width-6);
}

View File

@ -0,0 +1 @@
yo :)

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512.001 512.001" style="enable-background:new 0 0 512.001 512.001;" xml:space="preserve">
<g>
<g>
<path d="M503.839,395.379l-195.7-338.962C297.257,37.569,277.766,26.315,256,26.315c-21.765,0-41.257,11.254-52.139,30.102
L8.162,395.378c-10.883,18.85-10.883,41.356,0,60.205c10.883,18.849,30.373,30.102,52.139,30.102h391.398
c21.765,0,41.256-11.254,52.14-30.101C514.722,436.734,514.722,414.228,503.839,395.379z M477.861,440.586
c-5.461,9.458-15.241,15.104-26.162,15.104H60.301c-10.922,0-20.702-5.646-26.162-15.104c-5.46-9.458-5.46-20.75,0-30.208
L229.84,71.416c5.46-9.458,15.24-15.104,26.161-15.104c10.92,0,20.701,5.646,26.161,15.104l195.7,338.962
C483.321,419.836,483.321,431.128,477.861,440.586z"/>
</g>
</g>
<g>
<g>
<rect x="241.001" y="176.01" width="29.996" height="149.982"/>
</g>
</g>
<g>
<g>
<path d="M256,355.99c-11.027,0-19.998,8.971-19.998,19.998s8.971,19.998,19.998,19.998c11.026,0,19.998-8.971,19.998-19.998
S267.027,355.99,256,355.99z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 477.175 477.175" style="enable-background:new 0 0 477.175 477.175;" xml:space="preserve">
<g>
<path d="M145.188,238.575l215.5-215.5c5.3-5.3,5.3-13.8,0-19.1s-13.8-5.3-19.1,0l-225.1,225.1c-5.3,5.3-5.3,13.8,0,19.1l225.1,225
c2.6,2.6,6.1,4,9.5,4s6.9-1.3,9.5-4c5.3-5.3,5.3-13.8,0-19.1L145.188,238.575z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 768 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M14.5 18h-10c-0.276 0-0.5-0.224-0.5-0.5s0.224-0.5 0.5-0.5h10c0.276 0 0.5 0.224 0.5 0.5s-0.224 0.5-0.5 0.5z"></path>
<path fill="#000000" d="M16.5 3c-0.276 0-0.5 0.224-0.5 0.5v15c0 0.276-0.224 0.5-0.5 0.5h-11c-0.827 0-1.5-0.673-1.5-1.5s0.673-1.5 1.5-1.5h9c0.827 0 1.5-0.673 1.5-1.5v-12c0-0.827-0.673-1.5-1.5-1.5h-10c-0.827 0-1.5 0.673-1.5 1.5v15c0 1.378 1.122 2.5 2.5 2.5h11c0.827 0 1.5-0.673 1.5-1.5v-15c0-0.276-0.224-0.5-0.5-0.5zM3.5 2h10c0.276 0 0.5 0.224 0.5 0.5v12c0 0.276-0.224 0.5-0.5 0.5h-9c-0.562 0-1.082 0.187-1.5 0.501v-13.001c0-0.276 0.224-0.5 0.5-0.5z"></path>
</svg>

After

Width:  |  Height:  |  Size: 916 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M17.5 20h-16c-0.827 0-1.5-0.673-1.5-1.5v-16c0-0.827 0.673-1.5 1.5-1.5h16c0.827 0 1.5 0.673 1.5 1.5v16c0 0.827-0.673 1.5-1.5 1.5zM1.5 2c-0.276 0-0.5 0.224-0.5 0.5v16c0 0.276 0.224 0.5 0.5 0.5h16c0.276 0 0.5-0.224 0.5-0.5v-16c0-0.276-0.224-0.5-0.5-0.5h-16z"></path>
<path fill="#000000" d="M6.5 17h-2c-0.276 0-0.5-0.224-0.5-0.5v-9c0-0.276 0.224-0.5 0.5-0.5h2c0.276 0 0.5 0.224 0.5 0.5v9c0 0.276-0.224 0.5-0.5 0.5zM5 16h1v-8h-1v8z"></path>
<path fill="#000000" d="M10.5 17h-2c-0.276 0-0.5-0.224-0.5-0.5v-12c0-0.276 0.224-0.5 0.5-0.5h2c0.276 0 0.5 0.224 0.5 0.5v12c0 0.276-0.224 0.5-0.5 0.5zM9 16h1v-11h-1v11z"></path>
<path fill="#000000" d="M14.5 17h-2c-0.276 0-0.5-0.224-0.5-0.5v-5c0-0.276 0.224-0.5 0.5-0.5h2c0.276 0 0.5 0.224 0.5 0.5v5c0 0.276-0.224 0.5-0.5 0.5zM13 16h1v-4h-1v4z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="612px" height="612px" viewBox="0 0 612 612" style="enable-background:new 0 0 612 612;" xml:space="preserve">
<g>
<g id="cloud-off">
<path d="M494.7,229.5c-17.851-86.7-94.351-153-188.7-153c-38.25,0-73.95,10.2-102,30.6l38.25,38.25
c17.85-12.75,40.8-17.85,63.75-17.85c76.5,0,140.25,63.75,140.25,140.25v12.75h38.25c43.35,0,76.5,33.15,76.5,76.5
c0,28.05-15.3,53.55-40.8,66.3l38.25,38.25C591.6,438.6,612,400.35,612,357C612,290.7,558.45,234.6,494.7,229.5z M76.5,109.65
l71.4,68.85C66.3,183.6,0,249.9,0,331.5c0,84.15,68.85,153,153,153h298.35l51,51l33.15-33.15L109.65,76.5L76.5,109.65z
M196.35,229.5l204,204H153c-56.1,0-102-45.9-102-102c0-56.1,45.9-102,102-102H196.35z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg height="638pt" viewBox="-20 -129 638.67144 638" width="638pt" xmlns="http://www.w3.org/2000/svg"><path d="m478.90625 132.8125c-4.785156.003906-9.5625.292969-14.3125.863281-12.894531-41.988281-51.628906-70.683593-95.550781-70.773437-10.933594-.011719-21.789063 1.804687-32.121094 5.363281-25.578125-55.308594-86.195313-85.367187-145.699219-72.25-59.511718 13.121094-101.867187 65.875-101.824218 126.808594.003906 10.53125 1.316406 21.019531 3.890624 31.222656-56.695312 8.65625-97.203124 59.46875-92.988281 116.667969 4.207031 57.203125 51.71875 101.542968 109.070313 101.796875h369.535156c66.191406 0 119.847656-53.660157 119.847656-119.851563s-53.65625-119.847656-119.847656-119.847656zm0 219.722656h-369.535156c-49.238282.214844-89.472656-39.253906-90.207032-88.488281-.730468-49.234375 38.304688-89.878906 87.53125-91.132813 3.195313-.089843 6.152344-1.703124 7.957032-4.339843 1.8125-2.640625 2.246094-5.980469 1.171875-8.992188-19.824219-56.730469 9.664062-118.855469 66.152343-139.367187 56.484376-20.511719 118.964844 8.226562 140.15625 64.460937.96875 2.609375 2.976563 4.691407 5.546876 5.753907 2.574218 1.0625 5.46875 1.003906 7.992187-.160157 10.457031-4.863281 21.84375-7.382812 33.371094-7.394531 38 .070312 70.722656 26.835938 78.3125 64.070312 1.085937 5.414063 6.359375 8.914063 11.765625 7.820313 6.511718-1.304687 13.136718-1.96875 19.785156-1.976563 55.160156 0 99.875 44.71875 99.875 99.871094 0 55.160156-44.714844 99.875-99.875 99.875zm0 0"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M7.631 19.702c-0.041 0-0.083-0.005-0.125-0.016-0.898-0.231-1.761-0.587-2.564-1.059-0.233-0.137-0.315-0.434-0.186-0.671 0.159-0.292 0.243-0.622 0.243-0.957 0-1.103-0.897-2-2-2-0.334 0-0.665 0.084-0.957 0.243-0.237 0.129-0.534 0.047-0.671-0.186-0.472-0.804-0.828-1.666-1.059-2.564-0.065-0.254 0.077-0.515 0.325-0.598 0.814-0.274 1.362-1.036 1.362-1.895s-0.547-1.621-1.362-1.895c-0.248-0.084-0.39-0.344-0.325-0.598 0.231-0.898 0.587-1.761 1.059-2.564 0.137-0.233 0.434-0.315 0.671-0.186 0.291 0.159 0.622 0.243 0.957 0.243 1.103 0 2-0.897 2-2 0-0.334-0.084-0.665-0.243-0.957-0.129-0.237-0.047-0.534 0.186-0.671 0.804-0.472 1.666-0.828 2.564-1.059 0.254-0.065 0.515 0.077 0.598 0.325 0.274 0.814 1.036 1.362 1.895 1.362s1.621-0.547 1.895-1.362c0.084-0.248 0.345-0.39 0.598-0.325 0.898 0.231 1.761 0.587 2.564 1.059 0.233 0.137 0.315 0.434 0.186 0.671-0.159 0.292-0.243 0.622-0.243 0.957 0 1.103 0.897 2 2 2 0.334 0 0.665-0.084 0.957-0.243 0.237-0.129 0.534-0.047 0.671 0.186 0.472 0.804 0.828 1.666 1.059 2.564 0.065 0.254-0.077 0.515-0.325 0.598-0.814 0.274-1.362 1.036-1.362 1.895s0.547 1.621 1.362 1.895c0.248 0.084 0.39 0.344 0.325 0.598-0.231 0.898-0.587 1.761-1.059 2.564-0.137 0.233-0.434 0.315-0.671 0.186-0.292-0.159-0.622-0.243-0.957-0.243-1.103 0-2 0.897-2 2 0 0.334 0.084 0.665 0.243 0.957 0.129 0.237 0.047 0.534-0.186 0.671-0.804 0.472-1.666 0.828-2.564 1.059-0.254 0.065-0.515-0.077-0.598-0.325-0.274-0.814-1.036-1.362-1.895-1.362s-1.621 0.547-1.895 1.362c-0.070 0.207-0.264 0.341-0.474 0.341zM10 17c1.127 0 2.142 0.628 2.655 1.602 0.52-0.161 1.026-0.369 1.51-0.622-0.108-0.314-0.164-0.646-0.164-0.98 0-1.654 1.346-3 3-3 0.334 0 0.666 0.056 0.98 0.164 0.253-0.484 0.462-0.989 0.622-1.51-0.974-0.512-1.602-1.527-1.602-2.655s0.628-2.142 1.602-2.655c-0.161-0.52-0.369-1.026-0.622-1.51-0.314 0.108-0.646 0.164-0.98 0.164-1.654 0-3-1.346-3-3 0-0.334 0.056-0.666 0.164-0.98-0.484-0.253-0.989-0.462-1.51-0.622-0.512 0.974-1.527 1.602-2.655 1.602s-2.142-0.628-2.655-1.602c-0.52 0.16-1.026 0.369-1.51 0.622 0.108 0.314 0.164 0.646 0.164 0.98 0 1.654-1.346 3-3 3-0.334 0-0.666-0.056-0.98-0.164-0.253 0.484-0.462 0.989-0.622 1.51 0.974 0.512 1.602 1.527 1.602 2.655s-0.628 2.142-1.602 2.655c0.16 0.52 0.369 1.026 0.622 1.51 0.314-0.108 0.646-0.164 0.98-0.164 1.654 0 3 1.346 3 3 0 0.334-0.056 0.666-0.164 0.98 0.484 0.253 0.989 0.462 1.51 0.622 0.512-0.974 1.527-1.602 2.655-1.602z"></path>
<path fill="#000000" d="M10 13c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3-1.346 3-3 3zM10 8c-1.103 0-2 0.897-2 2s0.897 2 2 2c1.103 0 2-0.897 2-2s-0.897-2-2-2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='iso-8859-1'?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 457.68 457.68" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 457.68 457.68">
<path d="m439.48,167.086v-111.249c0-17.81-14.49-32.3-32.3-32.3h-374.88c-17.811,0-32.3,14.49-32.3,32.3v226.63c0,17.81 14.49,32.3 32.3,32.3h106.243l-12.162,13.09h-18.221c-4.142,0-7.5,3.358-7.5,7.5s3.358,7.5 7.5,7.5h104.361v72.334c0,10.449 8.501,18.951 18.951,18.951h80.627c10.449,0 18.951-8.501 18.951-18.951v-15.234h100.94c14.166,0 25.69-11.529 25.69-25.7v-182.59c0-11.563-7.674-21.364-18.2-24.581zm3.2,24.581v2.049h-172.49v-2.049c0-5.9 4.8-10.7 10.7-10.7h151.1c5.895,0.001 10.69,4.801 10.69,10.7zm-130.581,63.364h-41.909v-46.315h172.49v148.491h-111.63v-83.226c0-10.449-8.502-18.95-18.951-18.95zm3.951,28.809h-88.528v-9.858c0-2.178 1.772-3.951 3.951-3.951h80.627c2.178,0 3.951,1.772 3.951,3.951v9.858zm108.429-220.503v102.63h-143.59c-14.171,0-25.7,11.529-25.7,25.7v63.364h-23.718c-10.441,0-18.936,8.488-18.949,18.926h-197.523v-210.62h409.48zm-196.959,235.503h88.528v91.495h-88.528v-91.495zm-195.221-260.303h374.88c6.85,2.13163e-14 12.765,4.012 15.565,9.8h-406.011c2.801-5.788 8.716-9.8 15.566-9.8zm-16.025,250.421h196.247v10.81h-180.222c-7.243-0.001-13.452-4.48-16.025-10.81zm130.582,38.899l12.162-13.09h53.503v13.09h-65.665zm165.242,91.286h-80.627c-2.178,0-3.951-1.772-3.951-3.951v-9.857h88.528v9.857c0.001,2.178-1.772,3.951-3.95,3.951zm119.891-34.185h-100.94v-12.75h111.63v2.05c0,5.899-4.795,10.7-10.69,10.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M441.156,322.876l-48.666-47.386c-3.319-3.243-8.619-3.234-11.93,0.017l-81.894,80.299V8.533
c0-4.71-3.823-8.533-8.533-8.533h-68.267c-4.71,0-8.533,3.823-8.533,8.533v347.273l-81.894-80.299
c-3.311-3.243-8.602-3.251-11.921-0.017l-48.666,47.386c-1.655,1.604-2.586,3.806-2.586,6.11c0,2.304,0.939,4.506,2.586,6.11
l179.2,174.481c1.655,1.613,3.806,2.423,5.948,2.423c2.15,0,4.292-0.811,5.956-2.423l179.2-174.481
c1.647-1.604,2.577-3.806,2.577-6.11C443.733,326.682,442.803,324.48,441.156,322.876z M255.991,491.563L89.028,328.986
l36.412-35.456l90.445,88.695c2.449,2.406,6.11,3.115,9.276,1.775c3.174-1.331,5.231-4.429,5.231-7.868V17.067h51.2v359.066
c0,3.439,2.065,6.537,5.231,7.868c3.166,1.34,6.818,0.631,9.276-1.775l90.445-88.695l36.42,35.456L255.991,491.563z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg height="512pt" viewBox="0 0 512 512" width="512pt" xmlns="http://www.w3.org/2000/svg"><path d="m218.667969 240h-202.667969c-8.832031 0-16-7.167969-16-16s7.167969-16 16-16h202.667969c8.832031 0 16 7.167969 16 16s-7.167969 16-16 16zm0 0"/><path d="m138.667969 320c-4.097657 0-8.191407-1.558594-11.308594-4.691406-6.25-6.253906-6.25-16.386719 0-22.636719l68.695313-68.691406-68.695313-68.671875c-6.25-6.253906-6.25-16.386719 0-22.636719s16.382813-6.25 22.636719 0l80 80c6.25 6.25 6.25 16.382813 0 22.636719l-80 80c-3.136719 3.132812-7.234375 4.691406-11.328125 4.691406zm0 0"/><path d="m341.332031 512c-23.53125 0-42.664062-19.136719-42.664062-42.667969v-384c0-18.238281 11.605469-34.515625 28.882812-40.511719l128.171875-42.730468c28.671875-8.789063 56.277344 12.480468 56.277344 40.578125v384c0 18.21875-11.605469 34.472656-28.863281 40.488281l-128.214844 42.753906c-4.671875 1.449219-9 2.089844-13.589844 2.089844zm128-480c-1.386719 0-2.558593.171875-3.816406.554688l-127.636719 42.558593c-4.183594 1.453125-7.210937 5.675781-7.210937 10.21875v384c0 7.277344 7.890625 12.183594 14.484375 10.113281l127.636718-42.558593c4.160157-1.453125 7.210938-5.675781 7.210938-10.21875v-384c0-5.867188-4.777344-10.667969-10.667969-10.667969zm0 0"/><path d="m186.667969 106.667969c-8.832031 0-16-7.167969-16-16v-32c0-32.363281 26.300781-58.667969 58.664062-58.667969h240c8.832031 0 16 7.167969 16 16s-7.167969 16-16 16h-240c-14.699219 0-26.664062 11.96875-26.664062 26.667969v32c0 8.832031-7.167969 16-16 16zm0 0"/><path d="m314.667969 448h-85.335938c-32.363281 0-58.664062-26.304688-58.664062-58.667969v-32c0-8.832031 7.167969-16 16-16s16 7.167969 16 16v32c0 14.699219 11.964843 26.667969 26.664062 26.667969h85.335938c8.832031 0 16 7.167969 16 16s-7.167969 16-16 16zm0 0"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M17.5 6h-16c-0.827 0-1.5 0.673-1.5 1.5v9c0 0.827 0.673 1.5 1.5 1.5h16c0.827 0 1.5-0.673 1.5-1.5v-9c0-0.827-0.673-1.5-1.5-1.5zM17.5 7c0.030 0 0.058 0.003 0.087 0.008l-7.532 5.021c-0.29 0.193-0.819 0.193-1.109 0l-7.532-5.021c0.028-0.005 0.057-0.008 0.087-0.008h16zM17.5 17h-16c-0.276 0-0.5-0.224-0.5-0.5v-8.566l7.391 4.927c0.311 0.207 0.71 0.311 1.109 0.311s0.798-0.104 1.109-0.311l7.391-4.927v8.566c0 0.276-0.224 0.5-0.5 0.5z"></path>
</svg>

After

Width:  |  Height:  |  Size: 777 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M11.5 8c0.276 0 0.5-0.224 0.5-0.5v-4c0-0.827-0.673-1.5-1.5-1.5h-9c-0.827 0-1.5 0.673-1.5 1.5v12c0 0.746 0.537 1.56 1.222 1.853l5.162 2.212c0.178 0.076 0.359 0.114 0.532 0.114 0.213-0 0.416-0.058 0.589-0.172 0.314-0.207 0.495-0.575 0.495-1.008v-1.5h2.5c0.827 0 1.5-0.673 1.5-1.5v-4c0-0.276-0.224-0.5-0.5-0.5s-0.5 0.224-0.5 0.5v4c0 0.276-0.224 0.5-0.5 0.5h-2.5v-9.5c0-0.746-0.537-1.56-1.222-1.853l-3.842-1.647h7.564c0.276 0 0.5 0.224 0.5 0.5v4c0 0.276 0.224 0.5 0.5 0.5zM6.384 5.566c0.322 0.138 0.616 0.584 0.616 0.934v12c0 0.104-0.028 0.162-0.045 0.173s-0.081 0.014-0.177-0.027l-5.162-2.212c-0.322-0.138-0.616-0.583-0.616-0.934v-12c0-0.079 0.018-0.153 0.051-0.22l5.333 2.286z"></path>
<path fill="#000000" d="M18.354 9.146l-3-3c-0.195-0.195-0.512-0.195-0.707 0s-0.195 0.512 0 0.707l2.146 2.146h-6.293c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h6.293l-2.146 2.146c-0.195 0.195-0.195 0.512 0 0.707 0.098 0.098 0.226 0.146 0.354 0.146s0.256-0.049 0.354-0.146l3-3c0.195-0.195 0.195-0.512 0-0.707z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M9.5 19c-0.084 0-0.167-0.021-0.243-0.063-0.116-0.065-2.877-1.611-5.369-4.082-0.196-0.194-0.197-0.511-0.003-0.707s0.511-0.197 0.707-0.003c1.979 1.962 4.186 3.346 4.908 3.776 0.723-0.431 2.932-1.817 4.908-3.776 0.196-0.194 0.513-0.193 0.707 0.003s0.193 0.513-0.003 0.707c-2.493 2.471-5.253 4.017-5.369 4.082-0.076 0.042-0.159 0.063-0.243 0.063z"></path>
<path fill="#000000" d="M1.279 11c-0.188 0-0.368-0.106-0.453-0.287-0.548-1.165-0.826-2.33-0.826-3.463 0-2.895 2.355-5.25 5.25-5.25 0.98 0 2.021 0.367 2.931 1.034 0.532 0.39 0.985 0.86 1.319 1.359 0.334-0.499 0.787-0.969 1.319-1.359 0.91-0.667 1.951-1.034 2.931-1.034 2.895 0 5.25 2.355 5.25 5.25 0 1.133-0.278 2.298-0.826 3.463-0.118 0.25-0.415 0.357-0.665 0.24s-0.357-0.415-0.24-0.665c0.485-1.031 0.731-2.053 0.731-3.037 0-2.343-1.907-4.25-4.25-4.25-1.703 0-3.357 1.401-3.776 2.658-0.068 0.204-0.259 0.342-0.474 0.342s-0.406-0.138-0.474-0.342c-0.419-1.257-2.073-2.658-3.776-2.658-2.343 0-4.25 1.907-4.25 4.25 0 0.984 0.246 2.006 0.731 3.037 0.118 0.25 0.010 0.548-0.24 0.665-0.069 0.032-0.141 0.048-0.212 0.048z"></path>
<path fill="#000000" d="M10.515 15c-0.005 0-0.009-0-0.013-0-0.202-0.004-0.569-0.109-0.753-0.766l-1.217-4.334-0.807 3.279c-0.158 0.643-0.525 0.778-0.73 0.8s-0.592-0.027-0.889-0.62l-0.606-1.211c-0.029-0.058-0.056-0.094-0.076-0.117-0.003 0.004-0.007 0.009-0.011 0.015-0.37 0.543-1.192 0.953-1.913 0.953h-1c-0.276 0-0.5-0.224-0.5-0.5s0.224-0.5 0.5-0.5h1c0.421 0 0.921-0.272 1.087-0.516 0.223-0.327 0.547-0.501 0.891-0.478 0.374 0.025 0.708 0.279 0.917 0.696l0.445 0.89 0.936-3.803c0.158-0.64 0.482-0.779 0.726-0.783s0.572 0.125 0.751 0.76l1.284 4.576 1.178-3.608c0.205-0.628 0.582-0.736 0.788-0.745s0.59 0.068 0.847 0.677l0.724 1.719c0.136 0.322 0.578 0.616 0.927 0.616h1.5c0.276 0 0.5 0.224 0.5 0.5s-0.224 0.5-0.5 0.5h-1.5c-0.747 0-1.559-0.539-1.849-1.228l-0.592-1.406-1.274 3.9c-0.207 0.634-0.566 0.733-0.771 0.733z"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<g>
<path d="M494.933,93.867H17.067C7.645,93.877,0.011,101.512,0,110.933v290.133c0.011,9.421,7.645,17.056,17.067,17.067h477.867
c9.421-0.011,17.056-7.646,17.067-17.067V110.933C511.989,101.512,504.355,93.877,494.933,93.867z M17.067,401.067V110.933h51.2
v25.6c0,4.713,3.82,8.533,8.533,8.533s8.533-3.82,8.533-8.533v-25.6H102.4v25.6c0,4.713,3.82,8.533,8.533,8.533
s8.533-3.82,8.533-8.533v-25.6h17.067v25.6c0,4.713,3.82,8.533,8.533,8.533s8.533-3.82,8.533-8.533v-25.6h17.067v25.6
c0,4.713,3.82,8.533,8.533,8.533s8.533-3.82,8.533-8.533v-25.6H204.8v25.6c0,4.713,3.821,8.533,8.533,8.533
c4.713,0,8.533-3.82,8.533-8.533v-25.6h17.067v25.6c0,4.713,3.82,8.533,8.533,8.533s8.533-3.82,8.533-8.533v-25.6h17.067v25.6
c0,4.713,3.82,8.533,8.533,8.533s8.533-3.82,8.533-8.533v-25.6h204.8l0.012,290.133H17.067z"/>
<path d="M452.267,162.133H59.733c-14.132,0.015-25.585,11.468-25.6,25.6V204.8c-0.001,2.263,0.898,4.434,2.499,6.035
c1.6,1.6,3.771,2.499,6.035,2.499H51.2v85.333h-8.533c-2.263-0.001-4.434,0.898-6.035,2.499c-1.6,1.6-2.499,3.771-2.499,6.035
v17.067c0.015,14.132,11.468,25.585,25.6,25.6h392.533c14.132-0.015,25.585-11.468,25.6-25.6V307.2
c0.001-2.263-0.898-4.434-2.499-6.035c-1.6-1.6-3.771-2.499-6.035-2.499H460.8v-85.333h8.533
c2.263,0.001,4.434-0.898,6.035-2.499c1.6-1.6,2.499-3.771,2.499-6.035v-17.067C477.851,173.601,466.399,162.149,452.267,162.133
z M460.8,196.267h-8.533c-2.263-0.001-4.434,0.898-6.035,2.499c-1.6,1.6-2.499,3.771-2.499,6.035v102.4
c-0.001,2.263,0.898,4.434,2.499,6.035c1.6,1.6,3.771,2.499,6.035,2.499h8.533v8.533c-0.005,4.711-3.823,8.529-8.533,8.533
H59.733c-4.711-0.005-8.529-3.822-8.533-8.533v-8.533h8.533c2.263,0.001,4.434-0.898,6.035-2.499
c1.6-1.6,2.499-3.771,2.499-6.035V204.8c0.001-2.263-0.898-4.434-2.499-6.035c-1.6-1.6-3.771-2.499-6.035-2.499H51.2v-8.533
c0.005-4.711,3.822-8.529,8.533-8.533h392.533c4.711,0.005,8.529,3.822,8.533,8.533V196.267z"/>
<path d="M418.133,196.267H93.867c-2.263-0.001-4.434,0.898-6.035,2.499c-1.6,1.6-2.499,3.771-2.499,6.035v102.4
c-0.001,2.263,0.898,4.434,2.499,6.035c1.6,1.6,3.771,2.499,6.035,2.499h324.267c2.263,0.001,4.434-0.898,6.035-2.499
c1.6-1.6,2.499-3.771,2.499-6.035V204.8c0.001-2.263-0.898-4.434-2.499-6.035C422.568,197.165,420.397,196.266,418.133,196.267z
M409.6,298.667H102.4v-85.333h307.2V298.667z"/>
<path d="M472.575,128.683c-2.06-0.942-4.427-0.942-6.487,0c-1.033,0.433-1.984,1.039-2.813,1.792
c-0.773,0.815-1.383,1.772-1.796,2.817c-1.122,2.625-0.843,5.638,0.741,8.013s4.259,3.789,7.114,3.762
c1.119,0.027,2.229-0.207,3.242-0.683c1.034-0.433,1.987-1.039,2.817-1.792c1.604-1.606,2.496-3.788,2.475-6.058
c-0.033-2.259-0.917-4.422-2.475-6.059C474.576,129.704,473.619,129.096,472.575,128.683z"/>
<path d="M475.392,369.408c-3.421-3.158-8.695-3.158-12.117,0c-0.755,0.829-1.363,1.782-1.796,2.817
c-1.575,3.761-0.255,8.11,3.144,10.362c3.399,2.252,7.919,1.771,10.768-1.145c1.615-1.564,2.511-3.727,2.475-5.975
c-0.014-1.115-0.246-2.216-0.683-3.242C476.77,371.181,476.162,370.225,475.392,369.408z"/>
<path d="M39.421,144.383c1.014,0.477,2.125,0.711,3.246,0.683c2.855,0.03,5.532-1.385,7.115-3.761
c1.584-2.376,1.86-5.39,0.735-8.014c-0.413-1.044-1.021-2-1.791-2.817c-0.83-0.753-1.783-1.359-2.817-1.792
c-2.06-0.942-4.427-0.942-6.487,0c-2.141,0.78-3.828,2.467-4.608,4.608c-1.357,3.176-0.647,6.858,1.795,9.301
C37.437,143.345,38.388,143.951,39.421,144.383z"/>
<path d="M48.725,369.408c-3.421-3.158-8.695-3.158-12.117,0c-0.755,0.829-1.363,1.782-1.796,2.817
c-1.575,3.761-0.255,8.11,3.144,10.362c3.399,2.252,7.919,1.771,10.768-1.145c1.615-1.564,2.511-3.727,2.475-5.975
c-0.014-1.115-0.246-2.216-0.683-3.242C50.104,371.181,49.496,370.225,48.725,369.408z"/>
<path d="M128,256c2.264,0.003,4.435-0.897,6.033-2.5l8.533-8.533c3.281-3.341,3.256-8.701-0.054-12.012
s-8.671-3.335-12.012-0.054l-8.533,8.533c-2.44,2.44-3.169,6.11-1.849,9.298C121.438,253.92,124.549,255.999,128,256z"/>
<path d="M136.534,273.067c0,2.263,0.899,4.433,2.5,6.033c1.6,1.601,3.77,2.5,6.033,2.5s4.433-0.899,6.033-2.5l34.133-34.133
c3.296-3.338,3.279-8.711-0.038-12.029c-3.317-3.317-8.691-3.334-12.029-0.038l-34.133,34.133
C137.433,268.633,136.534,270.804,136.534,273.067z"/>
</g>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M185.379,185.379h-70.621c-4.873,0-8.828,3.955-8.828,8.828v211.862c0,4.873,3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828V194.207C194.207,189.334,190.252,185.379,185.379,185.379z M176.552,397.241h-52.966V203.034
h52.966V397.241z"/>
</g>
</g>
<g>
<g>
<path d="M291.31,97.103H220.69c-4.873,0-8.828,3.955-8.828,8.828v300.138c0,4.873,3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828V105.931C300.138,101.058,296.183,97.103,291.31,97.103z M282.483,397.241h-52.966V114.759
h52.966V397.241z"/>
</g>
</g>
<g>
<g>
<path d="M397.241,397.241h-70.621c-4.873,0-8.828,3.955-8.828,8.828s3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828S402.114,397.241,397.241,397.241z"/>
</g>
</g>
<g>
<g>
<path d="M503.172,397.241h-70.621c-4.873,0-8.828,3.955-8.828,8.828s3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828S508.045,397.241,503.172,397.241z"/>
</g>
</g>
<g>
<g>
<path d="M79.448,273.655H8.828c-4.873,0-8.828,3.955-8.828,8.828v123.586c0,4.873,3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828V282.483C88.276,277.61,84.321,273.655,79.448,273.655z M70.621,397.241H17.655V291.31h52.966
V397.241z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M19.104 0.896c-0.562-0.562-1.309-0.871-2.104-0.871s-1.542 0.309-2.104 0.871l-12.75 12.75c-0.052 0.052-0.091 0.114-0.116 0.183l-2 5.5c-0.066 0.183-0.021 0.387 0.116 0.524 0.095 0.095 0.223 0.146 0.354 0.146 0.057 0 0.115-0.010 0.171-0.030l5.5-2c0.069-0.025 0.131-0.065 0.183-0.116l12.75-12.75c0.562-0.562 0.871-1.309 0.871-2.104s-0.309-1.542-0.871-2.104zM5.725 17.068l-4.389 1.596 1.596-4.389 11.068-11.068 2.793 2.793-11.068 11.068zM18.396 4.396l-0.896 0.896-2.793-2.793 0.896-0.896c0.373-0.373 0.869-0.578 1.396-0.578s1.023 0.205 1.396 0.578c0.373 0.373 0.578 0.869 0.578 1.396s-0.205 1.023-0.578 1.396z"></path>
</svg>

After

Width:  |  Height:  |  Size: 957 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M9.5 12c-0.276 0-0.5-0.224-0.5-0.5v-9c0-0.276 0.224-0.5 0.5-0.5s0.5 0.224 0.5 0.5v9c0 0.276-0.224 0.5-0.5 0.5z"></path>
<path fill="#000000" d="M9.5 19c-2.003 0-3.887-0.78-5.303-2.197s-2.197-3.3-2.197-5.303c0-1.648 0.525-3.212 1.517-4.523 0.96-1.268 2.324-2.215 3.84-2.666 0.265-0.079 0.543 0.072 0.622 0.337s-0.072 0.543-0.337 0.622c-2.733 0.814-4.643 3.376-4.643 6.231 0 3.584 2.916 6.5 6.5 6.5s6.5-2.916 6.5-6.5c0-2.855-1.909-5.417-4.643-6.231-0.265-0.079-0.415-0.357-0.337-0.622s0.357-0.415 0.622-0.337c1.517 0.451 2.88 1.398 3.84 2.666 0.993 1.311 1.517 2.875 1.517 4.523 0 2.003-0.78 3.887-2.197 5.303s-3.3 2.197-5.303 2.197z"></path>
</svg>

After

Width:  |  Height:  |  Size: 984 B

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M16.218 3.782c-1.794-1.794-4.18-2.782-6.718-2.782s-4.923 0.988-6.718 2.782-2.782 4.18-2.782 6.717 0.988 4.923 2.782 6.718 4.18 2.782 6.718 2.782 4.923-0.988 6.718-2.782 2.782-4.18 2.782-6.718-0.988-4.923-2.782-6.717zM9.5 19c-4.687 0-8.5-3.813-8.5-8.5s3.813-8.5 8.5-8.5c4.687 0 8.5 3.813 8.5 8.5s-3.813 8.5-8.5 8.5z"></path>
<path fill="#000000" d="M9.5 15c-0.276 0-0.5-0.224-0.5-0.5v-2c0-0.276 0.224-0.5 0.5-0.5 1.93 0 3.5-1.57 3.5-3.5s-1.57-3.5-3.5-3.5-3.5 1.57-3.5 3.5c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5c0-2.481 2.019-4.5 4.5-4.5s4.5 2.019 4.5 4.5c0 2.312-1.753 4.223-4 4.472v1.528c0 0.276-0.224 0.5-0.5 0.5z"></path>
<path fill="#000000" d="M9.5 18c-0 0 0 0 0 0-0.276 0-0.5-0.224-0.5-0.5v-1c0-0.276 0.224-0.5 0.5-0.5 0 0 0 0 0 0 0.276 0 0.5 0.224 0.5 0.5v1c0 0.276-0.224 0.5-0.5 0.5z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M458.667,301.24H53.333C23.936,301.24,0,325.176,0,354.573v42.667c0,29.397,23.936,53.333,53.333,53.333h405.333
c29.397,0,53.333-23.915,53.333-53.333v-42.667C512,325.176,488.064,301.24,458.667,301.24z M490.667,397.24
c0,17.643-14.357,32-32,32H53.333c-17.643,0-32-14.357-32-32v-42.667c0-17.643,14.357-32,32-32h405.333c17.643,0,32,14.357,32,32
V397.24z"/>
</g>
</g>
<g>
<g>
<path d="M373.333,151.907c-5.888,0-10.667,4.779-10.667,10.667v149.333c0,5.888,4.779,10.667,10.667,10.667
c5.888,0,10.667-4.757,10.667-10.667V162.573C384,156.685,379.221,151.907,373.333,151.907z"/>
</g>
</g>
<g>
<g>
<circle cx="74.667" cy="375.907" r="10.667"/>
</g>
</g>
<g>
<g>
<circle cx="138.667" cy="375.907" r="10.667"/>
</g>
</g>
<g>
<g>
<circle cx="202.667" cy="375.907" r="10.667"/>
</g>
</g>
<g>
<g>
<circle cx="266.667" cy="375.907" r="10.667"/>
</g>
</g>
<g>
<g>
<path d="M437.333,365.24H330.667c-5.888,0-10.667,4.779-10.667,10.667c0,5.888,4.779,10.667,10.667,10.667h106.667
c5.888,0,10.667-4.779,10.667-10.667C448,370.019,443.221,365.24,437.333,365.24z"/>
</g>
</g>
<g>
<g>
<path d="M341.333,162.573c0-8.555,3.328-16.576,9.365-22.635c4.16-4.16,4.16-10.923,0-15.083c-4.16-4.16-10.923-4.16-15.083,0
C325.547,134.925,320,148.323,320,162.573c0,14.251,5.547,27.648,15.616,37.717c2.091,2.069,4.821,3.115,7.552,3.115
c2.731,0,5.461-1.024,7.531-3.115c4.16-4.16,4.16-10.923,0-15.083C344.661,179.149,341.333,171.128,341.333,162.573z"/>
</g>
</g>
<g>
<g>
<path d="M411.051,124.856c-4.16-4.16-10.923-4.16-15.083,0c-4.16,4.16-4.16,10.923,0,15.083c6.037,6.059,9.365,14.08,9.365,22.635
c0,8.555-3.328,16.597-9.387,22.635c-4.16,4.16-4.16,10.923,0,15.083c2.091,2.069,4.821,3.115,7.552,3.115
c2.731,0,5.461-1.024,7.552-3.115c10.069-10.069,15.616-23.467,15.616-37.717C426.667,148.323,421.12,134.925,411.051,124.856z"/>
</g>
</g>
<g>
<g>
<path d="M320.512,109.795c4.16-4.16,4.16-10.923,0-15.083c-4.16-4.16-10.923-4.16-15.083,0c-37.419,37.44-37.419,98.325,0,135.765
c2.091,2.069,4.821,3.115,7.552,3.115s5.461-1.045,7.531-3.115c4.16-4.16,4.16-10.923,0-15.083
C291.413,186.275,291.413,138.915,320.512,109.795z"/>
</g>
</g>
<g>
<g>
<path d="M441.216,94.712c-4.16-4.16-10.923-4.16-15.083,0c-4.16,4.16-4.16,10.923,0,15.083c29.099,29.12,29.099,76.48,0,105.6
c-4.16,4.16-4.16,10.923,0,15.083c2.091,2.069,4.821,3.115,7.552,3.115s5.44-1.045,7.531-3.115
C478.635,193.037,478.635,132.152,441.216,94.712z"/>
</g>
</g>
<g>
<g>
<path d="M290.347,79.629c4.16-4.16,4.16-10.923,0-15.083c-4.16-4.16-10.923-4.16-15.083,0
c-54.059,54.059-54.059,142.037,0,196.096c2.091,2.069,4.821,3.115,7.552,3.115c2.731,0,5.461-1.045,7.531-3.115
c4.16-4.16,4.16-10.923,0-15.083C244.587,199.821,244.587,125.389,290.347,79.629z"/>
</g>
</g>
<g>
<g>
<path d="M471.381,64.547c-4.16-4.16-10.923-4.16-15.083,0c-4.16,4.16-4.16,10.923,0,15.083c45.76,45.739,45.76,120.171,0,165.931
c-4.16,4.16-4.16,10.923,0,15.083c2.091,2.069,4.821,3.115,7.552,3.115c2.731,0,5.461-1.045,7.531-3.115
C525.44,206.584,525.44,118.605,471.381,64.547z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 511.998 511.998" style="enable-background:new 0 0 511.998 511.998;" xml:space="preserve">
<g>
<g>
<path d="M106.342,266.716c-6.611-6.61-15.4-10.251-24.749-10.251s-18.138,3.641-24.748,10.251
c-6.611,6.61-10.252,15.399-10.252,24.748s3.641,18.139,10.251,24.748c6.61,6.611,15.4,10.251,24.749,10.251
s18.138-3.641,24.748-10.251c6.61-6.61,10.251-15.399,10.251-24.749C116.592,282.114,112.951,273.325,106.342,266.716z
M92.199,302.071c-2.833,2.833-6.6,4.393-10.606,4.393c-4.006,0-7.773-1.56-10.607-4.394c-2.833-2.832-4.393-6.599-4.393-10.605
c0-4.006,1.56-7.772,4.394-10.606c2.833-2.833,6.6-4.394,10.606-4.394c4.006,0,7.773,1.56,10.606,4.394
c2.833,2.832,4.393,6.599,4.393,10.605C96.592,295.471,95.032,299.237,92.199,302.071z"/>
</g>
</g>
<g>
<g>
<path d="M509.015,133.452c-2.598-2.561-6.386-3.505-9.882-2.461l-168.392,50.26l50.261-168.392
c1.044-3.496,0.101-7.283-2.462-9.881c-2.561-2.597-6.331-3.593-9.846-2.601l-43.658,12.366c-2.838,0.804-5.176,2.819-6.389,5.508
L231.738,210.8l-88.673,26.466c-20.645-24.538-53.972-34.731-85.176-25.415C15.06,224.635-9.385,269.879,3.398,312.709
c10.484,35.126,42.802,57.886,77.716,57.886c7.657,0,15.439-1.095,23.143-3.394c29.942-8.937,51.731-34.055,56.782-64.247
l43.711,4.289l4.289,43.71c-30.192,5.05-55.311,26.839-64.249,56.782c-12.783,42.83,11.663,88.075,54.492,100.858
c7.569,2.259,15.379,3.405,23.212,3.405c17.091,0,34.128-5.596,47.974-15.756c14.32-10.508,24.581-25.08,29.673-42.14
c9.313-31.204-0.878-64.531-25.416-85.176l26.466-88.672l192.551-86.909c2.688-1.213,4.703-3.551,5.507-6.389l12.366-43.657
C512.61,139.787,511.613,136.015,509.015,133.452z M202.895,274.702c-0.72,1.594-1.009,3.349-0.838,5.09l0.702,7.158
l-49.673-4.872c-2.749-0.268-5.485,0.609-7.563,2.427s-3.311,4.414-3.408,7.173c-0.921,26.205-18.434,48.854-43.578,56.358
c-32.266,9.629-66.346-8.786-75.975-41.047c-9.628-32.263,8.785-66.344,41.048-75.974c25.29-7.549,52.427,1.906,67.527,23.526
c2.47,3.535,6.929,5.088,11.058,3.855l78.656-23.477L202.895,274.702z M253.594,369.796c-1.233,4.131,0.321,8.588,3.856,11.057
c21.62,15.102,31.075,42.24,23.527,67.528c-7.665,25.68-31.714,43.616-58.483,43.616c-5.895,0-11.78-0.865-17.492-2.569
c-32.262-9.629-50.676-43.711-41.047-75.974c7.505-25.145,30.154-42.658,56.359-43.579c2.759-0.097,5.356-1.33,7.174-3.408
c0.972-1.11,1.665-2.411,2.068-3.798c0.146-0.29,0.284-0.585,0.404-0.893l23.889-61.474c2-5.148-0.551-10.942-5.699-12.943
c-5.149-2.003-10.943,0.551-12.943,5.699l-9.383,24.145l-3.601-36.707l112.74-249.779l21.669-6.138L253.594,369.796z
M481.274,177.029L308.76,254.895l15.142-50.731l163.51-48.802L481.274,177.029z"/>
</g>
</g>
<g>
<g>
<path d="M247.206,406.197c-6.61-6.61-15.399-10.25-24.748-10.25s-18.138,3.64-24.748,10.251
c-6.611,6.61-10.251,15.399-10.251,24.748s3.64,18.138,10.251,24.748c6.61,6.611,15.399,10.251,24.748,10.251
s18.138-3.64,24.749-10.251C260.852,442.048,260.852,419.844,247.206,406.197z M233.066,441.552
c-2.834,2.833-6.601,4.394-10.607,4.394c-4.006,0-7.773-1.561-10.607-4.393c-2.832-2.833-4.393-6.6-4.393-10.606
s1.561-7.773,4.393-10.606c2.833-2.833,6.6-4.393,10.607-4.393c4.007,0,7.774,1.56,10.607,4.393
C238.913,426.188,238.913,435.704,233.066,441.552z"/>
</g>
</g>
<g>
<g>
<path d="M259.025,259.351c-5.151-1.997-10.943,0.559-12.939,5.709l-0.117,0.302c-1.997,5.149,0.56,10.942,5.709,12.938
c1.188,0.46,2.41,0.679,3.612,0.679c4.008,0,7.791-2.427,9.326-6.388l0.117-0.302C266.73,267.14,264.174,261.348,259.025,259.351z
"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M185.379,185.379h-70.621c-4.873,0-8.828,3.955-8.828,8.828v211.862c0,4.873,3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828V194.207C194.207,189.334,190.252,185.379,185.379,185.379z M176.552,397.241h-52.966V203.034
h52.966V397.241z"/>
</g>
</g>
<g>
<g>
<path d="M291.31,97.103H220.69c-4.873,0-8.828,3.955-8.828,8.828v300.138c0,4.873,3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828V105.931C300.138,101.058,296.183,97.103,291.31,97.103z M282.483,397.241h-52.966V114.759
h52.966V397.241z"/>
</g>
</g>
<g>
<g>
<path d="M397.241,397.241h-70.621c-4.873,0-8.828,3.955-8.828,8.828s3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828S402.114,397.241,397.241,397.241z"/>
</g>
</g>
<g>
<g>
<path d="M503.172,397.241h-70.621c-4.873,0-8.828,3.955-8.828,8.828s3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828S508.045,397.241,503.172,397.241z"/>
</g>
</g>
<g>
<g>
<path d="M79.448,273.655H8.828c-4.873,0-8.828,3.955-8.828,8.828v123.586c0,4.873,3.955,8.828,8.828,8.828h70.621
c4.873,0,8.828-3.955,8.828-8.828V282.483C88.276,277.61,84.321,273.655,79.448,273.655z M70.621,397.241H17.655V291.31h52.966
V397.241z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg width="64" version="1.1" xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 0 64 64" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 64 64">
<g>
<g fill="#1D1D1B">
<path d="M32,0C14.355,0,0,14.355,0,32s14.355,32,32,32s32-14.355,32-32S49.645,0,32,0z M32,60 C16.561,60,4,47.439,4,32S16.561,4,32,4s28,12.561,28,28S47.439,60,32,60z"/>
<circle cx="20.518" cy="21.361" r="4.338"/>
<circle cx="43.48" cy="21.361" r="4.338"/>
<path d="m52.541,36.568c-1.053-0.316-2.172,0.287-2.488,1.344-0.035,0.119-3.739,11.947-18.053,11.947-14.219,0-17.904-11.467-18.055-11.955-0.32-1.055-1.441-1.65-2.486-1.336-1.059,0.317-1.66,1.432-1.344,2.489 0.045,0.148 4.627,14.802 21.885,14.802s21.84-14.654 21.885-14.803c0.316-1.056-0.285-2.171-1.344-2.488z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 511.996 511.996" style="enable-background:new 0 0 511.996 511.996;" xml:space="preserve">
<g>
<g>
<path d="M441.154,176.9L261.954,2.419c-3.311-3.226-8.593-3.226-11.904,0L70.85,176.9c-1.655,1.604-2.586,3.806-2.586,6.11
c0,2.304,0.939,4.506,2.586,6.11l48.666,47.386c3.319,3.243,8.61,3.234,11.921-0.017l81.894-80.299v347.273
c0,4.71,3.823,8.533,8.533,8.533h68.267c4.71,0,8.533-3.823,8.533-8.533V156.19l81.894,80.299c3.311,3.251,8.61,3.243,11.93,0.017
l48.666-47.386c1.647-1.604,2.577-3.806,2.577-6.11C443.731,180.706,442.801,178.505,441.154,176.9z M386.549,218.466
l-90.445-88.695c-2.449-2.406-6.11-3.115-9.276-1.775c-3.166,1.331-5.231,4.429-5.231,7.868v359.066h-51.2V135.863
c0-3.439-2.057-6.536-5.231-7.868c-3.166-1.34-6.818-0.631-9.276,1.775l-90.445,88.695L89.035,183.01L255.998,20.433
L422.97,183.01L386.549,218.466z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M9.5 11c-3.033 0-5.5-2.467-5.5-5.5s2.467-5.5 5.5-5.5 5.5 2.467 5.5 5.5-2.467 5.5-5.5 5.5zM9.5 1c-2.481 0-4.5 2.019-4.5 4.5s2.019 4.5 4.5 4.5c2.481 0 4.5-2.019 4.5-4.5s-2.019-4.5-4.5-4.5z"></path>
<path fill="#000000" d="M17.5 20h-16c-0.827 0-1.5-0.673-1.5-1.5 0-0.068 0.014-1.685 1.225-3.3 0.705-0.94 1.67-1.687 2.869-2.219 1.464-0.651 3.283-0.981 5.406-0.981s3.942 0.33 5.406 0.981c1.199 0.533 2.164 1.279 2.869 2.219 1.211 1.615 1.225 3.232 1.225 3.3 0 0.827-0.673 1.5-1.5 1.5zM9.5 13c-3.487 0-6.060 0.953-7.441 2.756-1.035 1.351-1.058 2.732-1.059 2.746 0 0.274 0.224 0.498 0.5 0.498h16c0.276 0 0.5-0.224 0.5-0.5-0-0.012-0.023-1.393-1.059-2.744-1.382-1.803-3.955-2.756-7.441-2.756z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
<path fill="#000000" d="M18.5 18h-11c-0.827 0-1.5-0.673-1.5-1.5 0-0.048 0.011-1.19 0.924-2.315 0.525-0.646 1.241-1.158 2.128-1.522 1.071-0.44 2.4-0.662 3.948-0.662s2.876 0.223 3.948 0.662c0.887 0.364 1.603 0.876 2.128 1.522 0.914 1.125 0.924 2.267 0.924 2.315 0 0.827-0.673 1.5-1.5 1.5zM7 16.503c0.001 0.275 0.225 0.497 0.5 0.497h11c0.275 0 0.499-0.223 0.5-0.497-0.001-0.035-0.032-0.895-0.739-1.734-0.974-1.157-2.793-1.768-5.261-1.768s-4.287 0.612-5.261 1.768c-0.707 0.84-0.738 1.699-0.739 1.734z"></path>
<path fill="#000000" d="M13 11c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4c0 2.206-1.794 4-4 4zM13 4c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3-1.346-3-3-3z"></path>
<path fill="#000000" d="M4.5 18h-3c-0.827 0-1.5-0.673-1.5-1.5 0-0.037 0.008-0.927 0.663-1.8 0.378-0.505 0.894-0.904 1.533-1.188 0.764-0.34 1.708-0.512 2.805-0.512 0.179 0 0.356 0.005 0.527 0.014 0.276 0.015 0.487 0.25 0.473 0.526s-0.25 0.488-0.526 0.473c-0.153-0.008-0.312-0.012-0.473-0.012-3.894 0-3.997 2.379-4 2.503 0.001 0.274 0.225 0.497 0.5 0.497h3c0.276 0 0.5 0.224 0.5 0.5s-0.224 0.5-0.5 0.5z"></path>
<path fill="#000000" d="M5 12c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3-1.346 3-3 3zM5 7c-1.103 0-2 0.897-2 2s0.897 2 2 2 2-0.897 2-2c0-1.103-0.897-2-2-2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg height="507pt" viewBox="0 -62 507.2 507" width="507pt" xmlns="http://www.w3.org/2000/svg"><path d="m219.46875 302.011719c-18.75 18.746093-18.753906 49.140625-.007812 67.886719 18.746093 18.746093 49.140624 18.746093 67.886718 0 18.742188-18.746094 18.742188-49.140626-.007812-67.886719-18.746094-18.738281-49.128906-18.738281-67.871094 0zm0 0"/><path d="m140.265625 238.8125c-4.15625 4.015625-5.824219 9.964844-4.363281 15.558594 1.464844 5.597656 5.835937 9.964844 11.429687 11.429687 5.59375 1.464844 11.542969-.203125 15.558594-4.363281 50.023437-49.902344 131-49.902344 181.023437 0 6.28125 6.0625 16.257813 5.976562 22.429688-.195312 6.171875-6.171876 6.257812-16.152344.195312-22.429688-62.523437-62.386719-163.746093-62.386719-226.273437 0zm0 0"/><path d="m253.402344 95.949219c-67.929688-.1875-133.113282 26.816406-181.007813 74.992187-5.808593 6.3125-5.605469 16.082032.460938 22.148438 6.066406 6.066406 15.835937 6.269531 22.148437.460937 87.480469-87.488281 229.320313-87.488281 316.800782 0 4.015624 4.15625 9.964843 5.824219 15.558593 4.359375 5.59375-1.460937 9.964844-5.832031 11.425781-11.425781 1.464844-5.59375-.203124-11.542969-4.363281-15.558594-47.898437-48.175781-113.085937-75.175781-181.023437-74.976562zm0 0"/><path d="m502.316406 103.035156c-137.5625-137.246094-360.261718-137.246094-497.824218 0-6.0625 6.28125-5.976563 16.257813.195312 22.429688s16.148438 6.257812 22.429688.195312c125.070312-124.738281 327.5-124.738281 452.574218 0 4.015625 4.160156 9.964844 5.828125 15.558594 4.363282 5.59375-1.464844 9.964844-5.832032 11.429688-11.425782 1.464843-5.59375-.203126-11.542968-4.363282-15.5625zm0 0"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

85
peach-web-lite/wlan_card Normal file
View File

@ -0,0 +1,85 @@
{%- else %}
<!-- NETWORK CARD -->
<div class="card center">
<!-- NETWORK INFO BOX -->
{%- if wlan_state == "up" %}
<div class="capsule capsule-container success-border">
<!-- NETWORK STATUS GRID -->
<div id="netInfoBox" class="two-grid" title="PeachCloud network mode and status">
<a class="link two-grid-top-right" href="/settings/network" title="Configure network settings">
<img id="configureNetworking" class="icon-small" src="/icons/cog.svg" alt="Configure">
</a>
<!-- NETWORK STATUS -->
<!-- left column -->
<!-- network mode icon with label -->
<div class="grid-column-1">
<img id="netModeIcon" class="center icon icon-active" src="/icons/wifi.svg" alt="WiFi online">
<label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="WiFi Client Status">ONLINE</label>
{%- else %}
<div class="capsule capsule-container warning-border">
<div id="netInfoBox" class="two-grid" title="PeachCloud network mode and status">
<a class="link two-grid-top-right" href="/settings/network" title="Configure network settings">
<img id="configureNetworking" class="icon-small" src="/icons/cog.svg" alt="Configure">
</a>
<div class="grid-column-1">
<img id="netModeIcon" class="center icon icon-inactive" src="/icons/wifi.svg" alt="WiFi offline">
<label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title="WiFi Client Status">OFFLINE</label>
{%- endif %}
</div>
<div class="grid-column-2">
<!-- right column -->
<!-- network mode, ssid & ip with labels -->
<label class="label-small font-gray" for="netMode" title="Network Mode">MODE</label>
<p id="netMode" class="card-text" title="Network Mode">WiFi Client</p>
<label class="label-small font-gray" for="netSsid" title="WiFi SSID">SSID</label>
<p id="netSsid" class="card-text" title="SSID">{{ wlan_ssid }}</p>
<label class="label-small font-gray" for="netIp" title="WiFi Client IP Address">IP</label>
<p id="netIp" class="card-text" title="IP">{{ wlan_ip }}</p>
</div>
</div>
<!-- horizontal dividing line -->
<hr>
<!-- SIGNAL AND TRAFFIC GRID -->
<!-- row of icons representing network statistics -->
<div class="three-grid card-container">
<div class="stack">
<img id="netSignal" class="icon icon-medium" alt="Signal" title="WiFi Signal (%)" src="/icons/low-signal.svg">
<div class="flex-grid" style="padding-top: 0.5rem;">
<label class="label-medium" for="netSignal" style="padding-right: 3px;" title="Signal strength of WiFi connection (%)">{% if wlan_rssi %}{{ wlan_rssi }}{% else %}0{% endif %}%</label>
</div>
<label class="label-small font-gray">SIGNAL</label>
</div>
<div class="stack">
<img id="dataDownload" class="icon icon-medium" alt="Download" title="WiFi download total" src="/icons/down-arrow.svg">
<div class="flex-grid" style="padding-top: 0.5rem;">
{%- if wlan_traffic %}
<!-- display wlan traffic data -->
<label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total in {{ wlan_traffic.rx_unit }}">{{ wlan_traffic.received }}</label>
<label class="label-small font-near-black">{{ wlan_traffic.rx_unit }}</label>
{%- else %}
<!-- no wlan traffic data to display -->
<label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total">0</label>
<label class="label-small font-near-black">MB</label>
{%- endif %}
</div>
<label class="label-small font-gray">DOWNLOAD</label>
</div>
<div class="stack">
<img id="dataUpload" class="icon icon-medium" alt="Upload" title="WiFi upload total" src="/icons/up-arrow.svg">
<div class="flex-grid" style="padding-top: 0.5rem;">
{%- if wlan_traffic %}
<!-- display wlan traffic data -->
<label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total in {{ wlan_traffic.tx_unit }}">{{ wlan_traffic.transmitted }}</label>
<label class="label-small font-near-black">{{ wlan_traffic.tx_unit }}</label>
{%- else %}
<!-- no wlan traffic data to display -->
<label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total">0</label>
<label class="label-small font-near-black">MB</label>
{%- endif %}
</div>
<label class="label-small font-gray">UPLOAD</label>
</div>
</div>
</div>
</div>
{%- endif -%}