commit 4557aa8251fafbd394062f0e15a325196692b6df Author: erichhasl Date: Thu Aug 31 22:40:53 2017 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59250e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.cgi +*.hi +*.o diff --git a/App/Error.hs b/App/Error.hs new file mode 100644 index 0000000..4425f6e --- /dev/null +++ b/App/Error.hs @@ -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 diff --git a/App/Info.hs b/App/Info.hs new file mode 100644 index 0000000..6167581 --- /dev/null +++ b/App/Info.hs @@ -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 diff --git a/App/Login.hs b/App/Login.hs new file mode 100644 index 0000000..da813b7 --- /dev/null +++ b/App/Login.hs @@ -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 diff --git a/App/Logout.hs b/App/Logout.hs new file mode 100644 index 0000000..319b9fc --- /dev/null +++ b/App/Logout.hs @@ -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 diff --git a/App/Private.hs b/App/Private.hs new file mode 100644 index 0000000..e74d37e --- /dev/null +++ b/App/Private.hs @@ -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 diff --git a/App/Startpage.hs b/App/Startpage.hs new file mode 100644 index 0000000..40727c6 --- /dev/null +++ b/App/Startpage.hs @@ -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 diff --git a/AppMonad.hs b/AppMonad.hs new file mode 100644 index 0000000..29b5b06 --- /dev/null +++ b/AppMonad.hs @@ -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 diff --git a/Database.hs b/Database.hs new file mode 100644 index 0000000..e7f1f06 --- /dev/null +++ b/Database.hs @@ -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 diff --git a/DatabaseData.hs b/DatabaseData.hs new file mode 100644 index 0000000..ba67df1 --- /dev/null +++ b/DatabaseData.hs @@ -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 diff --git a/DatabaseTest.hs b/DatabaseTest.hs new file mode 100644 index 0000000..3149b24 --- /dev/null +++ b/DatabaseTest.hs @@ -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 diff --git a/Main.hs b/Main.hs new file mode 100644 index 0000000..9e729c5 --- /dev/null +++ b/Main.hs @@ -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 diff --git a/Request.hs b/Request.hs new file mode 100644 index 0000000..9cbfbfd --- /dev/null +++ b/Request.hs @@ -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 diff --git a/Routing.hs b/Routing.hs new file mode 100644 index 0000000..d0c881e --- /dev/null +++ b/Routing.hs @@ -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 diff --git a/Shared.hs b/Shared.hs new file mode 100644 index 0000000..55cfa6d --- /dev/null +++ b/Shared.hs @@ -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 +(<->) = ($) diff --git a/Template.hs b/Template.hs new file mode 100644 index 0000000..efc678c --- /dev/null +++ b/Template.hs @@ -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!" diff --git a/TestHeist.hs b/TestHeist.hs new file mode 100644 index 0000000..acf050f --- /dev/null +++ b/TestHeist.hs @@ -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 diff --git a/haskell.conf b/haskell.conf new file mode 100644 index 0000000..cc62ad1 --- /dev/null +++ b/haskell.conf @@ -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; + } +} diff --git a/spawn.sh b/spawn.sh new file mode 100755 index 0000000..6813e32 --- /dev/null +++ b/spawn.sh @@ -0,0 +1,4 @@ +#!/bin/bash +ghc --make -o Main.cgi Main.hs + +sudo service haskweb restart diff --git a/templates/base.tpl b/templates/base.tpl new file mode 100644 index 0000000..256abe2 --- /dev/null +++ b/templates/base.tpl @@ -0,0 +1,30 @@ + + + <pagetitle/> + + + + + + +
+ + + +
+ +
+ +
+ + + + + + + + diff --git a/templates/confirm_logout.tpl b/templates/confirm_logout.tpl new file mode 100644 index 0000000..07dbe08 --- /dev/null +++ b/templates/confirm_logout.tpl @@ -0,0 +1,12 @@ + +
+

Logout

+ +

Willst du dich wirklich abmelden?

+ +
+ +
+
+ +
diff --git a/templates/css/base.css b/templates/css/base.css new file mode 100644 index 0000000..65c247b --- /dev/null +++ b/templates/css/base.css @@ -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; +} diff --git a/templates/css/logo.png b/templates/css/logo.png new file mode 100644 index 0000000..57a3c3d Binary files /dev/null and b/templates/css/logo.png differ diff --git a/templates/css/logo.svg b/templates/css/logo.svg new file mode 100644 index 0000000..105970a --- /dev/null +++ b/templates/css/logo.svg @@ -0,0 +1,1027 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/css/nav-back.png b/templates/css/nav-back.png new file mode 100644 index 0000000..f6eb4b8 Binary files /dev/null and b/templates/css/nav-back.png differ diff --git a/templates/error404.tpl b/templates/error404.tpl new file mode 100644 index 0000000..bcc5330 --- /dev/null +++ b/templates/error404.tpl @@ -0,0 +1,13 @@ + +
+

Seite nicht gefunden

+

+ Die seite existiert nicht. Vielleicht war der Link veraltet. + Hier kommst du wieder zur Startseite. +

+ +

+ Bei weiteren Problemene kannst du dich an den Systemadministrator wenden. +

+
+
diff --git a/templates/footer.tpl b/templates/footer.tpl new file mode 100644 index 0000000..49c6c81 --- /dev/null +++ b/templates/footer.tpl @@ -0,0 +1,2 @@ +

Powered by Haskell und FastCGI

+

Copyright (C) Christian Merten 2017

diff --git a/templates/header.tpl b/templates/header.tpl new file mode 100644 index 0000000..b52a001 --- /dev/null +++ b/templates/header.tpl @@ -0,0 +1,15 @@ +
+
+ +

Haskweb

+ + +
+
diff --git a/templates/info.tpl b/templates/info.tpl new file mode 100644 index 0000000..d453d6b --- /dev/null +++ b/templates/info.tpl @@ -0,0 +1,3 @@ + +

Hier gibts tolle Infos!

+
diff --git a/templates/logged_in.tpl b/templates/logged_in.tpl new file mode 100644 index 0000000..b8e3d82 --- /dev/null +++ b/templates/logged_in.tpl @@ -0,0 +1,7 @@ + +
+

Login

+

Du bist angemeldet!

+

Hier kannst du dich abmelden.

+
+
diff --git a/templates/logged_out.tpl b/templates/logged_out.tpl new file mode 100644 index 0000000..05e1d7d --- /dev/null +++ b/templates/logged_out.tpl @@ -0,0 +1,8 @@ + +
+

Login

+

Du bist abgemeldet!

+

Hier kannst du dich anmelden.

+
+ +
diff --git a/templates/login.tpl b/templates/login.tpl new file mode 100644 index 0000000..e9923ea --- /dev/null +++ b/templates/login.tpl @@ -0,0 +1,20 @@ + +
+

Login

+ +

+ Hier kannst du dich anmelden, um auf geschützte Bereiche zuzugreifen.
+ Du hast noch keine Account? Dann kann du dich hier registrieren.
+

+ +

+

+ Benutzername:
+
+ Passwort:
+

+ +
+
+ +
diff --git a/templates/private.tpl b/templates/private.tpl new file mode 100644 index 0000000..b793634 --- /dev/null +++ b/templates/private.tpl @@ -0,0 +1,9 @@ + +
+

Geheimes Zeug

+

+ Jetzt kannst du die spannenden Sachen sehen, für die + man angemeldet sein muss! +

+
+
diff --git a/templates/register.tpl b/templates/register.tpl new file mode 100644 index 0000000..da7bede --- /dev/null +++ b/templates/register.tpl @@ -0,0 +1,21 @@ + +
+

Registrierung

+

+ Hier kannst du dich registrieren.
+ Du hast schon einen Account? Dann melde dich hier an.
+

+ +

+

+ Benutzername:
+
+ Passwort:
+
+ Passwort bestätigen:
+

+ +
+
+ +
diff --git a/templates/registered.tpl b/templates/registered.tpl new file mode 100644 index 0000000..b446073 --- /dev/null +++ b/templates/registered.tpl @@ -0,0 +1,10 @@ + +
+

Registrierung abgeschlossen

+

+ Du hast dich erfolgreich registriert.
+ Hier gehts zur Anmeldung. +

+
+ +
diff --git a/templates/startpage.tpl b/templates/startpage.tpl new file mode 100644 index 0000000..2c8b97a --- /dev/null +++ b/templates/startpage.tpl @@ -0,0 +1,25 @@ + +
+

Startseite

+ +

+ Das ist die Startseite der tollen Webseite! + Du wart hier heute schon mal!
+ 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. +

+ +

Neuigkeiten

+

+ 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! +

+ +

Lala

+

Ab und zu steht hier auch mal sinnvolles Zeug, meistens aber nur Müll!

+ +
+