| @@ -0,0 +1,3 @@ | |||
| *.cgi | |||
| *.hi | |||
| *.o | |||
| @@ -0,0 +1,19 @@ | |||
| module App.Error ( | |||
| PageNotFound(..) | |||
| ) where | |||
| import Network.FastCGI | |||
| import Network.URI | |||
| import Template | |||
| import Request | |||
| data PageNotFound = PageNotFound | |||
| instance RequestHandler PageNotFound where | |||
| handle handler = do | |||
| uri <- requestURI | |||
| let path = uriPath uri | |||
| res <- render "error404" [ ("pagetitle", "Seite nicht gefunden") | |||
| , ("path", path)] | |||
| output res | |||
| @@ -0,0 +1,16 @@ | |||
| {-# LANGUAGE OverloadedStrings #-} | |||
| module App.Info ( | |||
| Info(..) | |||
| ) where | |||
| import Request | |||
| import Template | |||
| import Network.FastCGI | |||
| data Info = Info | |||
| instance RequestHandler Info where | |||
| handle handler = do | |||
| result <- render "info" [("pagetitle", "Info")] | |||
| output result | |||
| @@ -0,0 +1,84 @@ | |||
| module App.Login ( | |||
| Login(..) | |||
| ) where | |||
| import Control.Monad.State (gets) | |||
| import Request | |||
| import Template | |||
| import Network.FastCGI | |||
| import AppMonad (numVisited, App) | |||
| import Database | |||
| import Shared | |||
| data Login = Login | |||
| | Register | |||
| instance RequestHandler Login where | |||
| handle Login = do | |||
| loginStatus <- isLoggedIn | |||
| if loginStatus then alreadyLoggedIn else login | |||
| handle Register = do | |||
| loginStatus <- isLoggedIn | |||
| if loginStatus then alreadyLoggedIn else register | |||
| loginSucceeded :: App CGIResult | |||
| loginSucceeded = do | |||
| mayFrom <- getInput "from" | |||
| case mayFrom of | |||
| Just from -> redirect from | |||
| Nothing -> | |||
| render "logged_in" [ ("pagetitle", "Angemeldet") | |||
| , ("kind", "jetzt") ] >>= output | |||
| loginFailed :: App CGIResult | |||
| loginFailed = do | |||
| result <- render "login" [("pagetitle", "Anmelden"), | |||
| ("errormsg", "Ungültige Anmeldedaten")] | |||
| output result | |||
| alreadyLoggedIn :: App CGIResult | |||
| alreadyLoggedIn = do | |||
| result <- render "logged_in" [ ("pagetitle", "Angemeldet") | |||
| , ("kind", "bereits") ] | |||
| output result | |||
| login :: App CGIResult | |||
| login = do | |||
| mayLogin <- sequence <$> sequence [getInput "username", getInput "password"] | |||
| case mayLogin of | |||
| Just [username, password] -> | |||
| validate username password <: loginSucceeded <-> loginFailed | |||
| Nothing -> do | |||
| result <- render "login" [ ("pagetitle", "Anmelden") | |||
| , ("errormsg", "") ] | |||
| output result | |||
| register :: App CGIResult | |||
| register = do | |||
| mayRegister <- sequence <$> sequence [ getInput "username" | |||
| , getInput "password" | |||
| , getInput "confirm_password" ] | |||
| case mayRegister of | |||
| Just [username, password, password_confirm] | |||
| | password /= password_confirm -> | |||
| registerFailed "Passwörter stimmen nicht überein!" | |||
| | otherwise -> isAvailable username <: | |||
| goRegister username password <-> | |||
| registerFailed "Benutername wird schon verwendet!" | |||
| Nothing -> do | |||
| res <- render "register" [ ("pagetitle", "Registrierung") | |||
| , ("errormsg", "") ] | |||
| output res | |||
| goRegister :: Username -> Password -> App CGIResult | |||
| goRegister username password = do | |||
| addUser username password | |||
| res <- render "registered" [("pagetitle", "Registrierung abgeschlossen")] | |||
| output res | |||
| registerFailed :: String -> App CGIResult | |||
| registerFailed reason = do | |||
| res <- render "register" [ ("pagetitle", "Registrierung") | |||
| , ("errormsg", reason ) ] | |||
| output res | |||
| @@ -0,0 +1,32 @@ | |||
| module App.Logout ( | |||
| Logout(..) | |||
| ) where | |||
| import Network.FastCGI | |||
| import Request | |||
| import Database | |||
| import AppMonad | |||
| import Template | |||
| data Logout = Logout | |||
| instance RequestHandler Logout where | |||
| handle handler = do | |||
| loginStatus <- isLoggedIn | |||
| if loginStatus then confirmLogout else redirect "/login" | |||
| confirmLogout :: App CGIResult | |||
| confirmLogout = do | |||
| method <- requestMethod | |||
| case method of | |||
| "POST" -> logout | |||
| _ -> render "confirm_logout" [ ("pagetitle", "Abmelden") ] >>= output | |||
| logout :: App CGIResult | |||
| logout = do | |||
| deleteCookie $ newCookie "username" "" | |||
| deleteCookie $ newCookie "login_string" "" | |||
| res <- render "logged_out" [ ("pagetitle", "Abgemeldet") | |||
| , ("kind", "jetzt")] | |||
| output res | |||
| @@ -0,0 +1,19 @@ | |||
| module App.Private ( | |||
| Private(..) | |||
| ) where | |||
| import Control.Monad.State (gets) | |||
| import Request | |||
| import Template | |||
| import Network.FastCGI | |||
| import AppMonad (numVisited, App) | |||
| import Database | |||
| data Private = Private | |||
| instance RequestHandler Private where | |||
| requireLogin _ = True | |||
| handle handler = do | |||
| res <- render "private" [ ("pagetitle", "Geheimes Zeug") ] | |||
| output res | |||
| @@ -0,0 +1,22 @@ | |||
| {-# LANGUAGE OverloadedStrings #-} | |||
| module App.Startpage ( | |||
| Startpage(..) | |||
| ) where | |||
| import Text.XHtml | |||
| import Control.Monad.State (gets) | |||
| import Request | |||
| import Template | |||
| import Network.FastCGI | |||
| import AppMonad (numVisited) | |||
| data Startpage = Startpage | |||
| instance RequestHandler Startpage where | |||
| handle handler = do | |||
| n <- gets numVisited | |||
| result <- render "startpage" [("pagetitle", "Startseite"), | |||
| ("visited", show n)] | |||
| output result | |||
| @@ -0,0 +1,48 @@ | |||
| {-# LANGUAGE FlexibleInstances, | |||
| GeneralizedNewtypeDeriving #-} | |||
| module AppMonad ( | |||
| AppState(..), App, runApp, setHeistState, setNumVisited | |||
| ) where | |||
| import Control.Monad.State | |||
| import Control.Monad.Catch | |||
| import Network.FastCGI | |||
| import Network.CGI.Monad | |||
| import Heist | |||
| import DatabaseData | |||
| data AppState = AppState { heist :: Maybe (HeistState App), | |||
| numVisited :: Int, | |||
| connection :: Connection } | |||
| setHeistState :: HeistState App -> AppState -> AppState | |||
| setHeistState hs as = as { heist = Just hs } | |||
| setNumVisited :: Int -> AppState -> AppState | |||
| setNumVisited n as = as { numVisited = n } | |||
| newtype AppT m a = App { getState :: StateT AppState (CGIT m) a } | |||
| deriving (Monad, MonadIO, MonadState AppState, | |||
| Applicative, Functor) | |||
| type App = AppT IO | |||
| instance MonadCGI (AppT IO) where | |||
| cgiAddHeader n v = App . lift $ cgiAddHeader n v | |||
| cgiGet x = App . lift $ cgiGet x | |||
| instance MonadThrow (AppT IO) where | |||
| throwM e = App . lift $ throwM e | |||
| instance MonadCatch (AppT IO) where | |||
| {-catch :: AppT IO a -> (e -> AppT IO a) -> AppT IO a-} | |||
| catch (App x) f = App $ catch x (getState . f) | |||
| runApp :: AppState -> App CGIResult -> IO () | |||
| runApp as x = do | |||
| let (App handled) = handleErrors x | |||
| y = evalStateT handled as | |||
| runFastCGIorCGI y | |||
| @@ -0,0 +1,41 @@ | |||
| {-# OPTIONS -Wall #-} | |||
| module Database ( | |||
| connect, addUser, validate, Connection, isLoggedIn, isAvailable, | |||
| Username, Password | |||
| ) where | |||
| import Control.Monad.State | |||
| import Network.CGI | |||
| import AppMonad | |||
| import DatabaseData | |||
| import Shared | |||
| validate :: Username -> Password -> App Bool | |||
| validate username password = do | |||
| c <- gets connection | |||
| isValid <- liftIO $ validateC c username password | |||
| when isValid $ do | |||
| setCookie $ newCookie "username" username | |||
| (Just str) <- liftIO $ getLoginStringC c username | |||
| setCookie $ newCookie "login_string" str | |||
| return isValid | |||
| isLoggedIn :: App Bool | |||
| isLoggedIn = defRunMaybeT False $ do | |||
| c <- gets connection | |||
| name <- MaybeT $ getCookie "username" | |||
| loginString <- MaybeT $ getCookie "login_string" | |||
| liftIO $ isLoggedInC c name loginString | |||
| isAvailable :: Username -> App Bool | |||
| isAvailable username = do | |||
| c <- gets connection | |||
| liftIO $ isAvailableC c username | |||
| addUser :: Username -> Password -> App () | |||
| addUser username password = do | |||
| c <- gets connection | |||
| liftIO $ addUserC c username password | |||
| @@ -0,0 +1,65 @@ | |||
| {-# LANGUAGE OverloadedStrings #-} | |||
| {-# OPTIONS -Wall #-} | |||
| module DatabaseData ( | |||
| connect, addUserC, validateC, Connection, Username, Password, | |||
| isLoggedInC, getLoginStringC, isAvailableC | |||
| ) where | |||
| import System.Random (newStdGen, randomRs) | |||
| import Control.Monad.Trans (liftIO) | |||
| import qualified Crypto.Hash as C (SHA3_512, Digest, hash) | |||
| import Data.ByteString.Char8 (ByteString, pack) | |||
| import Database.HDBC | |||
| import Database.HDBC.PostgreSQL | |||
| import Shared | |||
| type Username = String | |||
| type Password = String | |||
| type Salt = String | |||
| connect :: IO Connection | |||
| connect = connectPostgreSQL "user=christian dbname=haskweb" | |||
| addUserC :: Connection -> Username -> Password -> IO () | |||
| addUserC conn username password = do | |||
| gen <- newStdGen | |||
| let salt = take 32 $ randomRs ('a', 'z') gen | |||
| hashed = hash (password ++ salt) | |||
| _ <- run conn | |||
| "INSERT INTO webuser (name, password, salt) VALUES (?, ?, ?)" | |||
| [toSql username, toSql hashed, toSql salt] | |||
| commit conn | |||
| getLoginC :: Connection -> Username -> MaybeT IO (Password, Salt) | |||
| getLoginC conn username = do | |||
| [[pwd, salt]] <- liftIO $ quickQuery' conn | |||
| "SELECT password, salt FROM webuser WHERE name=?" | |||
| [toSql username] | |||
| return (fromSql pwd, fromSql salt) | |||
| validateC :: Connection -> Username -> Password -> IO Bool | |||
| validateC conn username password = defRunMaybeT False (do | |||
| (pwd, salt) <- getLoginC conn username | |||
| return $ pwd == hash (password ++ salt) ) | |||
| isLoggedInC :: Connection -> Username -> String -> IO Bool | |||
| isLoggedInC conn username loginString = defRunMaybeT False (do | |||
| (pwd, _) <- getLoginC conn username | |||
| return $ hash (pwd ++ username) == loginString) | |||
| getLoginStringC :: Connection -> Username -> IO (Maybe String) | |||
| getLoginStringC conn username = runMaybeT $ do | |||
| (pwd, _) <- getLoginC conn username | |||
| return $ hash (pwd ++ username) | |||
| isAvailableC :: Connection -> Username -> IO Bool | |||
| isAvailableC conn username = do | |||
| res <- quickQuery' conn "SELECT 1 FROM webuser WHERE name=?" [toSql username] | |||
| return $ length res == 0 | |||
| hash :: String -> String | |||
| hash = show . (C.hash :: ByteString -> C.Digest C.SHA3_512) . pack | |||
| @@ -0,0 +1,44 @@ | |||
| {-# LANGUAGE OverloadedStrings #-} | |||
| {-# OPTIONS -Wall #-} | |||
| import System.Random | |||
| import Control.Monad.Trans.Maybe | |||
| import Control.Monad (guard) | |||
| import Control.Monad.Trans | |||
| import Data.Maybe | |||
| import qualified Crypto.Hash as C (SHA3_512, Digest, hash) | |||
| import Data.ByteString.Char8 (ByteString, pack) | |||
| import Database.HDBC | |||
| import Database.HDBC.PostgreSQL | |||
| type Username = String | |||
| type Password = String | |||
| connect :: IO Connection | |||
| connect = connectPostgreSQL "user=christian dbname=haskweb" | |||
| addUser :: Connection -> Username -> Password -> IO () | |||
| addUser conn username password = do | |||
| gen <- newStdGen | |||
| let salt = take 32 $ randomRs ('a', 'z') gen | |||
| hashed = hash (password ++ salt) | |||
| _ <- run conn | |||
| "INSERT INTO webuser (name, password, salt) VALUES (?, ?, ?)" | |||
| [toSql username, toSql hashed, toSql salt] | |||
| commit conn | |||
| validate :: Connection -> Username -> Password -> IO Bool | |||
| validate conn username password = fromMaybe False <$> runMaybeT (do | |||
| res <- liftIO $ quickQuery' conn | |||
| "SELECT password, salt FROM webuser WHERE name=?" | |||
| [toSql username] | |||
| guard $ length res == 1 | |||
| guard $ length (head res) == 2 | |||
| let [pwd, salt] = head res | |||
| guard $ fromSql pwd == hash (password ++ fromSql salt) | |||
| return True) | |||
| hash :: String -> String | |||
| hash = show . (C.hash :: ByteString -> C.Digest C.SHA3_512) . pack | |||
| @@ -0,0 +1,27 @@ | |||
| import Data.Maybe | |||
| import Control.Monad.State (modify, gets) | |||
| import Network.FastCGI | |||
| import Network.URI | |||
| import Network.CGI | |||
| import Routing | |||
| import AppMonad | |||
| import Template | |||
| import Database | |||
| import Shared | |||
| import Text.XHtml | |||
| cgiMain :: App CGIResult | |||
| cgiMain = do | |||
| initTemplates | |||
| (fromMaybe 0) <$> read <$$> getCookie "visited" >>= modify . setNumVisited | |||
| newCookie "visited" . show . (+1) <$> gets numVisited >>= setCookie | |||
| uri <- requestURI | |||
| routeRequest uri | |||
| main :: IO () | |||
| main = do | |||
| conn <- connect | |||
| let as = AppState Nothing 0 conn | |||
| runApp as cgiMain | |||
| @@ -0,0 +1,35 @@ | |||
| {-# LANGUAGE ExistentialQuantification #-} | |||
| module Request ( | |||
| RequestHandler(..), handlePath | |||
| ) where | |||
| import Network.URI | |||
| import Network.FastCGI | |||
| import Text.Regex.PCRE | |||
| import Database | |||
| import AppMonad | |||
| class RequestHandler a where | |||
| handle :: a -> App CGIResult | |||
| requireLogin :: a -> Bool | |||
| requireLogin _ = False | |||
| handleRequest :: a -> App CGIResult | |||
| handleRequest x = do | |||
| status <- isLoggedIn | |||
| path <- uriPath <$> requestURI | |||
| if not status && requireLogin x | |||
| then redirect $ "/login?from=" ++ path | |||
| else handle x | |||
| data RH = forall h. RequestHandler h => RH h | |||
| instance RequestHandler RH where | |||
| requireLogin (RH x) = requireLogin x | |||
| handle (RH x) = handle x | |||
| handlePath :: RequestHandler a => String -> String -> a -> Maybe RH | |||
| handlePath path pattern handler | |||
| | path =~ pattern = Just (RH handler) | |||
| | otherwise = Nothing | |||
| @@ -0,0 +1,31 @@ | |||
| module Routing ( | |||
| routeRequest | |||
| ) where | |||
| import Control.Applicative ((<|>)) | |||
| import Network.URI | |||
| import Network.FastCGI | |||
| import App.Startpage | |||
| import App.Info | |||
| import App.Login | |||
| import App.Logout | |||
| import App.Error | |||
| import App.Private | |||
| import Request | |||
| import AppMonad | |||
| routeRequest :: URI -> App CGIResult | |||
| routeRequest uri = do | |||
| let path = uriPath uri | |||
| mayHandler = handlePath path "^/info" Info | |||
| <|> handlePath path "^/login" Login | |||
| <|> handlePath path "^/logout" Logout | |||
| <|> handlePath path "^/register" Register | |||
| <|> handlePath path "^/private" Private | |||
| <|> handlePath path "^/$" Startpage | |||
| case mayHandler of | |||
| Just handler -> handleRequest handler | |||
| Nothing -> handleRequest PageNotFound | |||
| @@ -0,0 +1,27 @@ | |||
| module Shared ( | |||
| defRunMaybeT, (<$$>), MaybeT(..), ifF, ifM, (<:), (<->) | |||
| ) where | |||
| import Data.Maybe | |||
| import Control.Monad.Trans.Maybe | |||
| defRunMaybeT :: Monad m => a -> MaybeT m a -> m a | |||
| defRunMaybeT defValue m = fromMaybe defValue <$> runMaybeT m | |||
| (<$$>) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b) | |||
| (<$$>) = fmap . fmap | |||
| ifF :: Bool -> a -> a -> a | |||
| ifF cond x y = if cond then x else y | |||
| ifF' :: a -> a -> Bool -> a | |||
| ifF' x y cond = if cond then x else y | |||
| ifM :: Monad m => m Bool -> m a -> m a -> m a | |||
| ifM cond m1 m2 = cond >>= ifF' m1 m2 | |||
| (<:) :: Monad m => m Bool -> m a -> m a -> m a | |||
| (<:) = ifM | |||
| (<->) :: (a -> b) -> a -> b | |||
| (<->) = ($) | |||
| @@ -0,0 +1,54 @@ | |||
| {-# LANGUAGE OverloadedStrings #-} | |||
| {-# OPTIONS -Wall #-} | |||
| module Template ( | |||
| initTemplates, render, Context | |||
| ) where | |||
| import Control.Monad.State | |||
| import Data.ByteString.Char8 (pack) | |||
| import Data.Binary.Builder | |||
| import Control.Lens (set) | |||
| import Data.ByteString.Lazy (toStrict) | |||
| import qualified Data.Text as T (pack, unpack) | |||
| import Data.Text.Encoding | |||
| import Heist | |||
| import Heist.Interpreted | |||
| import AppMonad | |||
| import Database | |||
| type TemplateName = String | |||
| type Context = [(String, String)] | |||
| basePath :: FilePath | |||
| basePath = "/home/christian/work/haskell/haskweb/" | |||
| heistConfig :: HeistConfig App | |||
| heistConfig = | |||
| (set hcNamespace "") $ | |||
| (set hcLoadTimeSplices defaultLoadTimeSplices) $ | |||
| (set hcTemplateLocations [loadTemplates $ basePath ++ "templates"]) $ | |||
| emptyHeistConfig | |||
| initTemplates :: App () | |||
| initTemplates = do | |||
| heistState <- liftIO $ either (error . concat) id | |||
| <$> initHeist heistConfig | |||
| modify $ setHeistState heistState | |||
| render :: TemplateName -> Context -> App String | |||
| render tpl oldContext = do | |||
| hs <- maybe (error "Template engine not initialized!") id <$> gets heist | |||
| loginStatus <- isLoggedIn | |||
| let context = ("login_path", if loginStatus then "/logout" else "/login") : | |||
| ("login_name", if loginStatus then "Abmelden" else "Anmelden") : | |||
| oldContext | |||
| hs' = foldr (\(k, v) h -> bindString (T.pack k) (T.pack v) h) hs context | |||
| mayBuilder <- renderTemplate hs' (pack tpl) | |||
| case mayBuilder of | |||
| Just (builder, _) -> | |||
| return . T.unpack . decodeUtf8 . toStrict . toLazyByteString $ builder | |||
| Nothing -> return $ "Could not load template!" | |||
| @@ -0,0 +1,23 @@ | |||
| {-# LANGUAGE OverloadedStrings #-} | |||
| import qualified Data.ByteString as B | |||
| import Blaze.ByteString.Builder (toByteStringIO) | |||
| import Control.Applicative | |||
| import Control.Monad.Trans.Either (runEitherT) | |||
| import Heist | |||
| import Heist.Compiled (renderTemplate) | |||
| import Control.Lens | |||
| heistConfig = | |||
| (set hcNamespace "") $ | |||
| -- (set hcInterpretedSplices defaultInterpretedSplices) $ | |||
| (set hcLoadTimeSplices defaultLoadTimeSplices) $ | |||
| (set hcTemplateLocations [loadTemplates "."]) $ | |||
| emptyHeistConfig | |||
| main = do | |||
| heistState <- either (error "oops") id <$> | |||
| (runEitherT $ initHeist heistConfig) | |||
| builder <- maybe (error "oops") fst $ | |||
| renderTemplate heistState "billy" | |||
| toByteStringIO B.putStr builder | |||
| @@ -0,0 +1,36 @@ | |||
| # configuration of the server | |||
| server { | |||
| # the port your site will be served on | |||
| listen 80; | |||
| # the domain name it will serve for | |||
| server_name .example.com; # substitute your machine's IP address or FQDN | |||
| charset utf-8; | |||
| # max upload size | |||
| client_max_body_size 75M; # adjust to taste | |||
| location /css { | |||
| alias /home/christian/work/haskell/haskweb/templates/css; | |||
| } | |||
| # Finally, send all non-media requests to the Django server. | |||
| location / { | |||
| #include /home/christian/work/repos/areion_web/uwsgi_params; | |||
| fastcgi_pass 127.0.0.1:9000; | |||
| fastcgi_param QUERY_STRING $query_string; | |||
| fastcgi_param REQUEST_METHOD $request_method; | |||
| fastcgi_param CONTENT_TYPE $content_type; | |||
| fastcgi_param CONTENT_LENGTH $content_length; | |||
| fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |||
| fastcgi_param SCRIPT_NAME $fastcgi_script_name; | |||
| fastcgi_param REQUEST_URI $request_uri; | |||
| fastcgi_param DOCUMENT_URI $document_uri; | |||
| fastcgi_param DOCUMENT_ROOT $document_root; | |||
| fastcgi_param SERVER_PROTOCOL $server_protocol; | |||
| fastcgi_param REMOTE_ADDR $remote_addr; | |||
| fastcgi_param REMOTE_PORT $remote_port; | |||
| fastcgi_param SERVER_ADDR $server_addr; | |||
| fastcgi_param SERVER_PORT $server_port; | |||
| fastcgi_param SERVER_NAME $server_name; | |||
| } | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| #!/bin/bash | |||
| ghc --make -o Main.cgi Main.hs | |||
| sudo service haskweb restart | |||
| @@ -0,0 +1,30 @@ | |||
| <html> | |||
| <head> | |||
| <title> <pagetitle/> </title> | |||
| <link href="/css/base.css" rel="stylesheet" type="text/css"> | |||
| <meta charset="utf8"> | |||
| </head> | |||
| <body> | |||
| <!-- START OF MAIN --> | |||
| <div id="main"> | |||
| <!-- HEADER --> | |||
| <div id="header"> | |||
| <apply template="header"/> | |||
| </div> | |||
| <div id="content"> | |||
| <apply-content /> | |||
| </div> | |||
| </div> | |||
| <!-- END OF MAIN --> | |||
| <!-- FOOTER --> | |||
| <div id="footer"> | |||
| <apply template="footer"/> | |||
| </div> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,12 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Logout</h2> | |||
| <p>Willst du dich wirklich abmelden?</p> | |||
| <form method="post"> | |||
| <input type="submit" value="Abmelden"> | |||
| </form> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,211 @@ | |||
| body,html{ | |||
| font-size: 16px; | |||
| font-family: "Georgia", serif; | |||
| line-height: 29px; | |||
| color:#0c1a30; | |||
| padding:0; | |||
| height: 100%; | |||
| margin: 0 0 1px; | |||
| } | |||
| #main{ | |||
| background:#fff scroll repeat-x 0px -100px; | |||
| margin: 0; | |||
| padding: 0; | |||
| width:100%; | |||
| min-height: 100%; | |||
| /*background: #FFFFFF;*/ | |||
| } | |||
| #content { | |||
| width: 960px; | |||
| margin: 0 auto; | |||
| overflow:hidden; | |||
| position: relative; | |||
| } | |||
| h1 { | |||
| font-size: 22px; | |||
| } | |||
| h2{ | |||
| font-size: 22px; | |||
| } | |||
| h3 a:link, h3 a:visited, h2 a:link, h2 a:visited { | |||
| color: #2C558A; | |||
| } | |||
| h3 { | |||
| font-size: 19px; | |||
| } | |||
| h4 { | |||
| font-size: 17px; | |||
| } | |||
| h1,h2,h3,h4{ | |||
| color: #2c558a; | |||
| font-weight: normal; | |||
| text-shadow: #fff 0px 1px 1px; | |||
| letter-spacing:0.1em; | |||
| font-family: "Gill Sans", "PT Sans", "Arial", sans-serif; | |||
| text-transform: uppercase; | |||
| } | |||
| ul { | |||
| list-style: none; | |||
| margin: 0 0 29px 10px; | |||
| padding-left: 10px; | |||
| text-indent: -13px; | |||
| } | |||
| p.error { | |||
| color: #EE3333; | |||
| } | |||
| a:link,a:visited{ | |||
| color: #2B7EA1; | |||
| text-decoration: none; | |||
| } | |||
| #footer { | |||
| color: green; | |||
| } | |||
| #header { | |||
| background-color: white; | |||
| } | |||
| #heading { | |||
| width: 100%; | |||
| background: #132742 url(nav-back.png) scroll repeat-x bottom left; | |||
| color: #C4E7FF; | |||
| padding: 25px 0 0 0; | |||
| margin:0; | |||
| height: 60px; | |||
| overflow:hidden; | |||
| } | |||
| #heading .inner{ | |||
| width: 960px; | |||
| margin:0 auto; | |||
| overflow:hidden; | |||
| line-height: 1em; | |||
| vertical-align: top; | |||
| height: 100px; | |||
| padding: 0; | |||
| position: relative; | |||
| /*height: 4em;*/ | |||
| } | |||
| #heading h1 { | |||
| margin:0; | |||
| padding: 0; | |||
| margin: 0 auto; | |||
| position: absolute; | |||
| top: 10px; | |||
| left: 50px; | |||
| vertical-align: top; | |||
| } | |||
| #heading img { | |||
| width: 32px; | |||
| margin:0; | |||
| padding: 0; | |||
| margin: 0 auto; | |||
| position: absolute; | |||
| top: 0px; | |||
| left: 0px; | |||
| vertical-align: top; | |||
| } | |||
| #title a { | |||
| color: fff; | |||
| } | |||
| #nav { | |||
| overflow: hidden; | |||
| color: #C4E7FF; | |||
| height: 36px; | |||
| padding: 4px 0 0 0; margin: 0; | |||
| float: right; | |||
| } | |||
| #nav ul { | |||
| padding: 0; | |||
| margin: 0; | |||
| height: 100%; | |||
| display: inline; | |||
| } | |||
| #nav li { | |||
| vertical-align: top; | |||
| padding: 15 0 10px 0; | |||
| height: 30px; | |||
| display: block; | |||
| list-style-type: none; | |||
| float: left; | |||
| letter-spacing: -0.01em; | |||
| margin: 0 20px; | |||
| font-family: "Droid Sans", sans-serif; | |||
| text-shadow: 1px 1px #142845; | |||
| } | |||
| #nav a:link, #nav a:visited { | |||
| color: inherit; | |||
| vertical-align: top; | |||
| height: 100%; | |||
| display: block; | |||
| } | |||
| #nav a:hover, #nav a:active { | |||
| color: #fff; | |||
| vertical-align: top; | |||
| height: 100%; | |||
| display: block; | |||
| } | |||
| a.nolink:link, a.nolink:visited { | |||
| text-decoration: none; | |||
| color: #FF3333; | |||
| } | |||
| a.nolink:hover { | |||
| text-decoration: underline; | |||
| } | |||
| .singlecolumn h2 { | |||
| line-height: 44px; | |||
| border-bottom: 1px solid #C2D1E1; | |||
| margin-bottom: 13px; | |||
| padding: 0px; | |||
| } | |||
| #footer { | |||
| position: relative; | |||
| top: 0; | |||
| left: 0; | |||
| width: 100%; | |||
| clear: both; | |||
| border-top: 1px solid #C2D1E1; | |||
| color: #a0acba; | |||
| padding: 4px 0px; | |||
| height: 31px; | |||
| font-family: "PT Sans", sans-serif; | |||
| font-size: 0.75em; | |||
| text-align: right; | |||
| background: #f5f9ff; | |||
| vertical-align: middle; | |||
| line-height: 31px; | |||
| overflow: hidden; | |||
| } | |||
| #footer p { | |||
| padding: 0 20px; | |||
| margin: 0; | |||
| } | |||
| #footer p.left { | |||
| float: left; | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Seite nicht gefunden</h2> | |||
| <p> | |||
| Die seite <path/> existiert nicht. Vielleicht war der Link veraltet. | |||
| <a href="/">Hier</a> kommst du wieder zur Startseite. | |||
| </p> | |||
| <p> | |||
| Bei weiteren Problemene kannst du dich an den Systemadministrator wenden. | |||
| </p> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,2 @@ | |||
| <p class="left">Powered by Haskell und FastCGI</p> | |||
| <p>Copyright (C) Christian Merten 2017</p> | |||
| @@ -0,0 +1,15 @@ | |||
| <div id="heading"> | |||
| <div class="inner"> | |||
| <img src="/css/logo.png"> | |||
| <h1 id="title"><a href="/">Haskweb</a></h1> | |||
| <div id="nav"> | |||
| <ul id="navlist"> | |||
| <li><a href="/">Home</a></li> | |||
| <li><a href="/info">About</a></li> | |||
| <li><a href="${login_path}"><login_name/></a></li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,3 @@ | |||
| <apply template="base"> | |||
| <p>Hier gibts tolle Infos!</p> | |||
| </apply> | |||
| @@ -0,0 +1,7 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Login</h2> | |||
| <p>Du bist <kind/> angemeldet!</p> | |||
| <p><a href="/logout">Hier</a> kannst du dich abmelden.</p> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,8 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Login</h2> | |||
| <p>Du bist <kind/> abgemeldet!</p> | |||
| <p><a href="/login">Hier</a> kannst du dich anmelden.</p> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,20 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Login</h2> | |||
| <p> | |||
| Hier kannst du dich anmelden, um auf geschützte Bereiche zuzugreifen.<br> | |||
| Du hast noch keine Account? Dann kann du dich <a href="/register">hier</a> registrieren.<br> | |||
| </p> | |||
| <p class="error"><errormsg/> | |||
| <form method="post"> | |||
| Benutzername:<br> | |||
| <input type="text" name="username"><br> | |||
| Passwort:<br> | |||
| <input type="password" name="password"><br><br> | |||
| <input type="submit" value="Anmelden"> | |||
| </form> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,9 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Geheimes Zeug</h2> | |||
| <p> | |||
| Jetzt kannst du die spannenden Sachen sehen, für die | |||
| man angemeldet sein muss! | |||
| </p> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,21 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Registrierung</h2> | |||
| <p> | |||
| Hier kannst du dich registrieren.<br> | |||
| Du hast schon einen Account? Dann melde dich <a href="/login">hier</a> an.<br> | |||
| </p> | |||
| <p class="error"><errormsg/> | |||
| <form method="post"> | |||
| Benutzername:<br> | |||
| <input type="text" name="username"><br> | |||
| Passwort:<br> | |||
| <input type="password" name="password"><br> | |||
| Passwort bestätigen:<br> | |||
| <input type="password" name="confirm_password"><br><br> | |||
| <input type="submit" value="Anmelden"> | |||
| </form> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,10 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Registrierung abgeschlossen</h2> | |||
| <p> | |||
| Du hast dich erfolgreich registriert.<br> | |||
| <a href="/login">Hier</a> gehts zur Anmeldung. | |||
| </p> | |||
| </div> | |||
| </apply> | |||
| @@ -0,0 +1,25 @@ | |||
| <apply template="base"> | |||
| <div class="singlecolumn"> | |||
| <h2>Startseite</h2> | |||
| <p> | |||
| Das ist die Startseite der tollen Webseite! | |||
| Du wart hier heute schon <b><visited/></b> mal!<br> | |||
| Schau dich hier ruhig um, es gibt total viel tolles zu sehen! Jede | |||
| Menge sinnloser Text! Vielleicht wird es hier in Zukunft auch | |||
| die ein oder andere Dokumentation zu sehen geben. | |||
| </p> | |||
| <h3>Neuigkeiten</h3> | |||
| <p> | |||
| Hier werden tolle Informationen veröffentlicht! Aktuell gibt es aber | |||
| nichts neues! Vielleicht ja später mal! Es lohnt sich definitiv | |||
| hier nicht regelmäßig vorbei zu schauen, da sich hier eh nichts | |||
| ändert! | |||
| </p> | |||
| <h3>Lala</h3> | |||
| <p>Ab und zu steht hier auch mal sinnvolles Zeug, meistens aber nur Müll!</p> | |||
| </div> | |||
| </apply> | |||