mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-03 16:59:22 +02:00
authenticate/proxy: add user impersonation, refresh, dashboard (#123)
proxy: Add user dashboard. [GH-123] proxy/authenticate: Add manual refresh of their session. [GH-73] authorize: Add administrator (super user) account support. [GH-110] internal/policy: Allow administrators to impersonate other users. [GH-110]
This commit is contained in:
parent
dc2eb9668c
commit
66b4c2d3cd
42 changed files with 1644 additions and 1006 deletions
|
@ -1,10 +1,7 @@
|
|||
package templates // import "github.com/pomerium/pomerium/internal/templates"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/version"
|
||||
)
|
||||
|
||||
// New loads html and style resources directly. Panics on failure.
|
||||
|
@ -17,129 +14,240 @@ func New() *template.Template {
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-size: 1em;
|
||||
line-height: 1.42857143;
|
||||
color: #333;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
p {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 40em;
|
||||
display: block;
|
||||
margin: 10% auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,'Helvetica Neue', sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background: #F8F8FF;
|
||||
}
|
||||
|
||||
#main {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
.content, .message, button {
|
||||
border: 1px solid rgba(0,0,0,.125);
|
||||
border-bottom-width: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
#info-box {
|
||||
max-width: 480px;
|
||||
width: 480px;
|
||||
margin-top: 200px;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0px;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
letter-spacing: 0.3px;
|
||||
text-transform: uppercase;
|
||||
color: #32325d;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
text-align: center;
|
||||
background: #F8F8FF;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 15px 0;
|
||||
color: #32325d;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 18px;
|
||||
font-weight: 650;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0 -30px;
|
||||
padding: 20px 30px 30px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.content, .message {
|
||||
background-color: #fff;
|
||||
padding: 2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.error, .message {
|
||||
border-bottom-color: #c00;
|
||||
}
|
||||
.message {
|
||||
padding: 1.5rem 2rem 1.3rem;
|
||||
}
|
||||
header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.075);
|
||||
margin: -2rem 0 2rem;
|
||||
padding: 2rem 0 1.8rem;
|
||||
}
|
||||
header h1 {
|
||||
font-size: 1.5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
.error header {
|
||||
color: #c00;
|
||||
}
|
||||
.details {
|
||||
font-size: .85rem;
|
||||
color: #999;
|
||||
}
|
||||
button {
|
||||
color: #fff;
|
||||
background-color: #3B8686;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
padding: 1rem 2.5rem;
|
||||
text-shadow: 0 3px 1px rgba(0,0,0,.2);
|
||||
outline: none;
|
||||
}
|
||||
button:active {
|
||||
border-top-width: 4px;
|
||||
border-bottom-width: 1px;
|
||||
text-shadow: none;
|
||||
}
|
||||
footer {
|
||||
font-size: 0.75em;
|
||||
color: #999;
|
||||
border: 1px solid #e8e8fb;
|
||||
background-color: #F8F8FF;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin-bottom: 20px;
|
||||
background: #FCFCFF;
|
||||
box-shadow: 0 1px 3px 0 rgba(50, 50, 93, 0.15), 0 4px 6px 0 rgba(112, 157, 199, 0.15);
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
fieldset label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 42px;
|
||||
padding: 10px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
fieldset label:not(:last-child) {
|
||||
border-bottom: 1px solid #f0f5fa;
|
||||
}
|
||||
|
||||
fieldset label span {
|
||||
min-width: 125px;
|
||||
padding: 0 15px;
|
||||
text-align: right;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
#group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
#group::before {
|
||||
display: inline-flex;
|
||||
content: '';
|
||||
height: 15px;
|
||||
background-position: -1000px -1000px;
|
||||
background-repeat: no-repeat;
|
||||
// margin-right: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-table;
|
||||
margin-top: -72px;
|
||||
background: #F8F8FF;
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 115px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ok{
|
||||
fill: #6E43E8;
|
||||
}
|
||||
|
||||
.error{
|
||||
fill: #EB292F;
|
||||
}
|
||||
|
||||
p.message {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.field {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
background: transparent;
|
||||
font-weight: 400;
|
||||
color: #31325f;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
fieldset .select::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 5px;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
margin-top: -2px;
|
||||
pointer-events: none;
|
||||
background: #6E43E8 url("data:image/svg+xml;utf8,<svg viewBox='0 0 140 140' width='24' height='24' xmlns='http://www.w3.org/2000/svg'><g><path d='m121.3,34.6c-1.6-1.6-4.2-1.6-5.8,0l-51,51.1-51.1-51.1c-1.6-1.6-4.2-1.6-5.8,0-1.6,1.6-1.6,4.2 0,5.8l53.9,53.9c0.8,0.8 1.8,1.2 2.9,1.2 1,0 2.1-0.4 2.9-1.2l53.9-53.9c1.7-1.6 1.7-4.2 0.1-5.8z' fill='white'/></g></svg>") no-repeat;
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.flex{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #FCFCFF;
|
||||
background: #6E43E8;
|
||||
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
font-weight: 700;
|
||||
width: 50%;
|
||||
height: 40px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button.half{
|
||||
flex-grow:0;
|
||||
flex-shrink:0;
|
||||
flex-basis:calc(50% - 10px);
|
||||
}
|
||||
|
||||
.button.full{
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 7px 14px 0 rgba(50, 50, 93, 0.1), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.off-color{
|
||||
background: #5735B5;
|
||||
}
|
||||
|
||||
</style>
|
||||
{{end}}`))
|
||||
t = template.Must(t.Parse(fmt.Sprintf(`{{define "footer.html"}}Secured by <b>pomerium</b> %s {{end}}`, version.FullVersion())))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "sign_in_message.html"}}
|
||||
{{if eq (len .AllowedDomains) 1}}
|
||||
{{if eq (index .AllowedDomains 0) "@*"}}
|
||||
<p>You may sign in with any {{.ProviderName}} account.</p>
|
||||
{{else}}
|
||||
<p>You may sign in with your <b>{{index .AllowedDomains 0}}</b> {{.ProviderName}} account.</p>
|
||||
{{end}}
|
||||
{{else if gt (len .AllowedDomains) 1}}
|
||||
<p>
|
||||
You may sign in with any of these {{.ProviderName}} accounts:<br>
|
||||
{{range $i, $e := .AllowedDomains}}{{if $i}}, {{end}}<b>{{$e}}</b>{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
{{end}}`))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "sign_in.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>Sign In</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<header>
|
||||
<h1>Sign in to <b>{{.Destination}}</b></h1>
|
||||
</header>
|
||||
|
||||
{{template "sign_in_message.html" .}}
|
||||
|
||||
<form method="GET" action="/start">
|
||||
<input type="hidden" name="redirect_uri" value="{{.Redirect}}">
|
||||
<button type="submit" class="btn">Sign in with {{.ProviderName}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<footer>{{template "footer.html"}} </footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}`))
|
||||
|
||||
template.Must(t.Parse(`
|
||||
|
@ -147,51 +255,116 @@ footer {
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>Error</title>
|
||||
<title>{{.Code}} - {{.Title}}</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content error">
|
||||
<header>
|
||||
<h1>{{.Title}}</h1>
|
||||
</header>
|
||||
<p>
|
||||
{{.Message}}<br>
|
||||
<span class="details">HTTP {{.Code}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<footer>{{template "footer.html"}} </footer>
|
||||
</div>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
<svg class="icon error" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"/></svg>
|
||||
<h1 class="title">{{.Title}}</h1>
|
||||
<section>
|
||||
<p class="message">{{.Message}}.</p>
|
||||
<p class="message">Troubleshoot your <a href="/.pomerium">session</a>.</p>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://www.pomerium.io" style="display: block;">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 139 30"><defs><style>.a {fill: #6e43e8;}.a,.b {fill-rule: evenodd;}.b,.c {fill: #fff;}</style></defs><title>powered-by-pomerium</title><path class="a" d="M10.6,5.5H138.4c3.09,0,5.6,2,5.6,4.39V31.11c0,2.42-2.51,4.39-5.6,4.39H10.6c-3.09,0-5.6-2-5.6-4.39V9.89C5,7.47,7.51,5.5,10.6,5.5Z" transform="translate(-5 -5.5)" /><path class="b" d="M75.4,26.62H73.94l1.13-2.79-2.25-5.69h1.54L75.78,22l1.43-3.87h1.54Zm-5.61-2.44a2.42,2.42,0,0,1-1.5-.55V24H66.78V15.56h1.51v3a2.48,2.48,0,0,1,1.5-.55c1.58,0,2.66,1.28,2.66,3.09S71.37,24.18,69.79,24.18Zm-.32-4.88a1.68,1.68,0,0,0-1.18.53v2.52a1.65,1.65,0,0,0,1.18.54c.85,0,1.44-.73,1.44-1.8S70.32,19.3,69.47,19.3Zm-8.8,4.33a2.38,2.38,0,0,1-1.5.55c-1.57,0-2.66-1.27-2.66-3.09S57.6,18,59.17,18a2.44,2.44,0,0,1,1.5.55v-3h1.52V24H60.67Zm0-3.8a1.63,1.63,0,0,0-1.17-.53c-.86,0-1.45.73-1.45,1.79s.59,1.8,1.45,1.8a1.6,1.6,0,0,0,1.17-.54Zm-9,1.68A1.69,1.69,0,0,0,53.47,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,53.11,18,2.66,2.66,0,0,1,55.7,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34a1.38,1.38,0,0,0-1.37,1.36h2.57A1.28,1.28,0,0,0,53.05,19.17Zm-5.34.93V24H46.2v-5.9h1.51v.59A2,2,0,0,1,49.16,18a1.65,1.65,0,0,1,.49.06v1.35a1.83,1.83,0,0,0-.53-.07A1.87,1.87,0,0,0,47.71,20.1ZM41,21.51A1.69,1.69,0,0,0,42.76,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,42.4,18,2.66,2.66,0,0,1,45,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34A1.38,1.38,0,0,0,41,20.53h2.57A1.28,1.28,0,0,0,42.34,19.17ZM35.7,24l-1.2-4-1.2,4H32l-2-5.9h1.51l1.19,4,1.19-4h1.37l1.19,4,1.19-4h1.51l-2,5.9Zm-9.23.14a2.94,2.94,0,0,1-3-3.09,3,3,0,1,1,6.07,0A2.94,2.94,0,0,1,26.47,24.18Zm0-4.92c-.88,0-1.49.75-1.49,1.83s.61,1.83,1.49,1.83S28,22.18,28,21.09,27.35,19.26,26.47,19.26Zm-6.62,1.87H18.49V24H17V15.93h2.87a2.61,2.61,0,1,1,0,5.2Zm-.22-4H18.49V19.9h1.14a1.38,1.38,0,1,0,0-2.75Z" transform="translate(-5 -5.5)" /><path class="c" d="M132.71,14.9A3.93,3.93,0,0,0,128.78,11H94.59a3.93,3.93,0,0,0-3.93,3.92V31.06h2.71V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2.47ZM93.37,19a5.49,5.49,0,1,1,11,0Zm12.95,0a5.49,5.49,0,1,1,11,0Zm12.94,0a5.49,5.49,0,1,1,11,0Z" transform="translate(-5 -5.5)" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>{{end}}`))
|
||||
</html>
|
||||
|
||||
{{end}}`))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "sign_out.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>Sign Out</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<header>
|
||||
<h1>Sign out of <b>{{.Destination}}</b></h1>
|
||||
</header>
|
||||
|
||||
<p>You're currently signed in as <b>{{.Email}}</b>. This will also sign you out of other internal apps.</p>
|
||||
<form method="POST" action="/sign_out">
|
||||
<input type="hidden" name="redirect_uri" value="{{.Redirect}}">
|
||||
<input type="hidden" name="sig" value="{{.Signature}}">
|
||||
<input type="hidden" name="ts" value="{{.Timestamp}}">
|
||||
<button type="submit">Sign out</button>
|
||||
</form>
|
||||
</div>
|
||||
<footer>{{template "footer.html"}}</footer>
|
||||
</div>
|
||||
</body>
|
||||
{{define "dashboard.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
|
||||
<head>
|
||||
<title>Pomerium</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
<svg class="icon ok" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
|
||||
</svg>
|
||||
<form method="POST" action="{{.SignoutURL}}">
|
||||
<section>
|
||||
<h2>Session</h2>
|
||||
<p class="message">Your current session details.</p>
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input name="email" type="email" class="field" value="{{.Email}}" disabled>
|
||||
</label>
|
||||
<label>
|
||||
<span>User</span>
|
||||
<input name="user" type="text" class="field" value="{{.User}}" disabled>
|
||||
</label>
|
||||
<label class="select">
|
||||
<span>Groups</span>
|
||||
<div id="group" class="field">
|
||||
<select name="group">
|
||||
{{range .Groups}}
|
||||
<option value="{{.}}">{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<span>Expiry</span>
|
||||
<input name="session expiration" type="text" class="field" value="{{.RefreshDeadline}}" disabled>
|
||||
</label>
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
<button class="button half" type="submit">Sign Out</button>
|
||||
<a href="/.pomerium/refresh" class="button half">Refresh</a>
|
||||
</div>
|
||||
</form>
|
||||
{{if .IsAdmin}}
|
||||
<form method="POST" action="/.pomerium/impersonate">
|
||||
<section>
|
||||
<h2>Sign-in-as</h2>
|
||||
<p class="message">Administrators can temporarily impersonate another a user.</p>
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input name="email" type="email" class="field" value="{{.ImpersonateEmail}}" placeholder="user@example.com">
|
||||
</label>
|
||||
<label>
|
||||
<span>Group</span>
|
||||
<input name="group" type="text" class="field" value="{{.ImpersonateGroup}}" placeholder="engineering">
|
||||
</label>
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
<input name="csrf" type="hidden" value="{{.CSRF}}">
|
||||
<button class="button full" type="submit">Impersonate session</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://www.pomerium.io" style="display: block;">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 139 30"><defs><style>.a {fill: #6e43e8;}.a,.b {fill-rule: evenodd;}.b,.c {fill: #fff;}</style></defs><title>powered-by-pomerium</title><path class="a" d="M10.6,5.5H138.4c3.09,0,5.6,2,5.6,4.39V31.11c0,2.42-2.51,4.39-5.6,4.39H10.6c-3.09,0-5.6-2-5.6-4.39V9.89C5,7.47,7.51,5.5,10.6,5.5Z" transform="translate(-5 -5.5)" /><path class="b" d="M75.4,26.62H73.94l1.13-2.79-2.25-5.69h1.54L75.78,22l1.43-3.87h1.54Zm-5.61-2.44a2.42,2.42,0,0,1-1.5-.55V24H66.78V15.56h1.51v3a2.48,2.48,0,0,1,1.5-.55c1.58,0,2.66,1.28,2.66,3.09S71.37,24.18,69.79,24.18Zm-.32-4.88a1.68,1.68,0,0,0-1.18.53v2.52a1.65,1.65,0,0,0,1.18.54c.85,0,1.44-.73,1.44-1.8S70.32,19.3,69.47,19.3Zm-8.8,4.33a2.38,2.38,0,0,1-1.5.55c-1.57,0-2.66-1.27-2.66-3.09S57.6,18,59.17,18a2.44,2.44,0,0,1,1.5.55v-3h1.52V24H60.67Zm0-3.8a1.63,1.63,0,0,0-1.17-.53c-.86,0-1.45.73-1.45,1.79s.59,1.8,1.45,1.8a1.6,1.6,0,0,0,1.17-.54Zm-9,1.68A1.69,1.69,0,0,0,53.47,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,53.11,18,2.66,2.66,0,0,1,55.7,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34a1.38,1.38,0,0,0-1.37,1.36h2.57A1.28,1.28,0,0,0,53.05,19.17Zm-5.34.93V24H46.2v-5.9h1.51v.59A2,2,0,0,1,49.16,18a1.65,1.65,0,0,1,.49.06v1.35a1.83,1.83,0,0,0-.53-.07A1.87,1.87,0,0,0,47.71,20.1ZM41,21.51A1.69,1.69,0,0,0,42.76,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,42.4,18,2.66,2.66,0,0,1,45,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34A1.38,1.38,0,0,0,41,20.53h2.57A1.28,1.28,0,0,0,42.34,19.17ZM35.7,24l-1.2-4-1.2,4H32l-2-5.9h1.51l1.19,4,1.19-4h1.37l1.19,4,1.19-4h1.51l-2,5.9Zm-9.23.14a2.94,2.94,0,0,1-3-3.09,3,3,0,1,1,6.07,0A2.94,2.94,0,0,1,26.47,24.18Zm0-4.92c-.88,0-1.49.75-1.49,1.83s.61,1.83,1.49,1.83S28,22.18,28,21.09,27.35,19.26,26.47,19.26Zm-6.62,1.87H18.49V24H17V15.93h2.87a2.61,2.61,0,1,1,0,5.2Zm-.22-4H18.49V19.9h1.14a1.38,1.38,0,1,0,0-2.75Z" transform="translate(-5 -5.5)" /><path class="c" d="M132.71,14.9A3.93,3.93,0,0,0,128.78,11H94.59a3.93,3.93,0,0,0-3.93,3.92V31.06h2.71V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2.47ZM93.37,19a5.49,5.49,0,1,1,11,0Zm12.95,0a5.49,5.49,0,1,1,11,0Zm12.94,0a5.49,5.49,0,1,1,11,0Z" transform="translate(-5 -5.5)" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}`))
|
||||
return t
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue