roughed in environment details (#107)

This commit is contained in:
Michael Quigley 2022-12-22 14:56:19 -05:00
parent a0fd3a9c63
commit 3856d6eb61
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
17 changed files with 182 additions and 174 deletions

View File

@ -20,9 +20,9 @@ func (h *environmentDetailHandler) Handle(params metadata.GetEnvironmentDetailPa
return metadata.NewGetEnvironmentDetailInternalServerError() return metadata.NewGetEnvironmentDetailInternalServerError()
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
senv, err := str.FindEnvironmentForAccount(params.Body.EnvZID, int(principal.ID), tx) senv, err := str.FindEnvironmentForAccount(params.EnvZID, int(principal.ID), tx)
if err != nil { if err != nil {
logrus.Errorf("environment '%v' not found for account '%v': %v", params.Body.EnvZID, principal.Email, err) logrus.Errorf("environment '%v' not found for account '%v': %v", params.EnvZID, principal.Email, err)
return metadata.NewGetEnvironmentDetailNotFound() return metadata.NewGetEnvironmentDetailNotFound()
} }
es := &rest_model_zrok.EnvironmentServices{ es := &rest_model_zrok.EnvironmentServices{

View File

@ -64,7 +64,7 @@ func (self *Store) FindEnvironmentsForAccount(accountId int, tx *sqlx.Tx) ([]*En
func (self *Store) FindEnvironmentForAccount(envZId string, accountId int, tx *sqlx.Tx) (*Environment, error) { func (self *Store) FindEnvironmentForAccount(envZId string, accountId int, tx *sqlx.Tx) (*Environment, error) {
env := &Environment{} env := &Environment{}
if err := tx.QueryRowx("select environments.* from environments where z_id = $1 and account_id = $1", envZId, accountId).StructScan(env); err != nil { if err := tx.QueryRowx("select environments.* from environments where z_id = $1 and account_id = $2", envZId, accountId).StructScan(env); err != nil {
return nil, errors.Wrap(err, "error finding environment by z_id and account_id") return nil, errors.Wrap(err, "error finding environment by z_id and account_id")
} }
return env, nil return env, nil

View File

@ -61,8 +61,8 @@ GetEnvironmentDetailParams contains all the parameters to send to the API endpoi
*/ */
type GetEnvironmentDetailParams struct { type GetEnvironmentDetailParams struct {
// Body. // EnvZID.
Body GetEnvironmentDetailBody EnvZID string
timeout time.Duration timeout time.Duration
Context context.Context Context context.Context
@ -117,15 +117,15 @@ func (o *GetEnvironmentDetailParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client o.HTTPClient = client
} }
// WithBody adds the body to the get environment detail params // WithEnvZID adds the envZID to the get environment detail params
func (o *GetEnvironmentDetailParams) WithBody(body GetEnvironmentDetailBody) *GetEnvironmentDetailParams { func (o *GetEnvironmentDetailParams) WithEnvZID(envZID string) *GetEnvironmentDetailParams {
o.SetBody(body) o.SetEnvZID(envZID)
return o return o
} }
// SetBody adds the body to the get environment detail params // SetEnvZID adds the envZId to the get environment detail params
func (o *GetEnvironmentDetailParams) SetBody(body GetEnvironmentDetailBody) { func (o *GetEnvironmentDetailParams) SetEnvZID(envZID string) {
o.Body = body o.EnvZID = envZID
} }
// WriteToRequest writes these params to a swagger request // WriteToRequest writes these params to a swagger request
@ -135,7 +135,9 @@ func (o *GetEnvironmentDetailParams) WriteToRequest(r runtime.ClientRequest, reg
return err return err
} }
var res []error var res []error
if err := r.SetBodyParam(o.Body); err != nil {
// path param envZId
if err := r.SetPathParam("envZId", o.EnvZID); err != nil {
return err return err
} }

View File

@ -6,13 +6,11 @@ package metadata
// Editing this file might prove futile when you re-run the swagger generate command // Editing this file might prove futile when you re-run the swagger generate command
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
) )
@ -94,11 +92,11 @@ func (o *GetEnvironmentDetailOK) IsCode(code int) bool {
} }
func (o *GetEnvironmentDetailOK) Error() string { func (o *GetEnvironmentDetailOK) Error() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailOK %+v", 200, o.Payload) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailOK %+v", 200, o.Payload)
} }
func (o *GetEnvironmentDetailOK) String() string { func (o *GetEnvironmentDetailOK) String() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailOK %+v", 200, o.Payload) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailOK %+v", 200, o.Payload)
} }
func (o *GetEnvironmentDetailOK) GetPayload() *rest_model_zrok.EnvironmentServices { func (o *GetEnvironmentDetailOK) GetPayload() *rest_model_zrok.EnvironmentServices {
@ -156,11 +154,11 @@ func (o *GetEnvironmentDetailUnauthorized) IsCode(code int) bool {
} }
func (o *GetEnvironmentDetailUnauthorized) Error() string { func (o *GetEnvironmentDetailUnauthorized) Error() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailUnauthorized ", 401) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailUnauthorized ", 401)
} }
func (o *GetEnvironmentDetailUnauthorized) String() string { func (o *GetEnvironmentDetailUnauthorized) String() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailUnauthorized ", 401) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailUnauthorized ", 401)
} }
func (o *GetEnvironmentDetailUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { func (o *GetEnvironmentDetailUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
@ -207,11 +205,11 @@ func (o *GetEnvironmentDetailNotFound) IsCode(code int) bool {
} }
func (o *GetEnvironmentDetailNotFound) Error() string { func (o *GetEnvironmentDetailNotFound) Error() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailNotFound ", 404) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailNotFound ", 404)
} }
func (o *GetEnvironmentDetailNotFound) String() string { func (o *GetEnvironmentDetailNotFound) String() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailNotFound ", 404) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailNotFound ", 404)
} }
func (o *GetEnvironmentDetailNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { func (o *GetEnvironmentDetailNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
@ -258,52 +256,14 @@ func (o *GetEnvironmentDetailInternalServerError) IsCode(code int) bool {
} }
func (o *GetEnvironmentDetailInternalServerError) Error() string { func (o *GetEnvironmentDetailInternalServerError) Error() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailInternalServerError ", 500) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailInternalServerError ", 500)
} }
func (o *GetEnvironmentDetailInternalServerError) String() string { func (o *GetEnvironmentDetailInternalServerError) String() string {
return fmt.Sprintf("[GET /detail/environment][%d] getEnvironmentDetailInternalServerError ", 500) return fmt.Sprintf("[GET /detail/environment/{envZId}][%d] getEnvironmentDetailInternalServerError ", 500)
} }
func (o *GetEnvironmentDetailInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { func (o *GetEnvironmentDetailInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil return nil
} }
/*
GetEnvironmentDetailBody get environment detail body
swagger:model GetEnvironmentDetailBody
*/
type GetEnvironmentDetailBody struct {
// env z Id
EnvZID string `json:"envZId,omitempty"`
}
// Validate validates this get environment detail body
func (o *GetEnvironmentDetailBody) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this get environment detail body based on context it is used
func (o *GetEnvironmentDetailBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *GetEnvironmentDetailBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *GetEnvironmentDetailBody) UnmarshalBinary(b []byte) error {
var res GetEnvironmentDetailBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}

View File

@ -50,7 +50,7 @@ func (a *Client) GetEnvironmentDetail(params *GetEnvironmentDetailParams, authIn
op := &runtime.ClientOperation{ op := &runtime.ClientOperation{
ID: "getEnvironmentDetail", ID: "getEnvironmentDetail",
Method: "GET", Method: "GET",
PathPattern: "/detail/environment", PathPattern: "/detail/environment/{envZId}",
ProducesMediaTypes: []string{"application/zrok.v1+json"}, ProducesMediaTypes: []string{"application/zrok.v1+json"},
ConsumesMediaTypes: []string{"application/zrok.v1+json"}, ConsumesMediaTypes: []string{"application/zrok.v1+json"},
Schemes: []string{"http"}, Schemes: []string{"http"},

View File

@ -74,7 +74,7 @@ func init() {
} }
} }
}, },
"/detail/environment": { "/detail/environment/{envZId}": {
"get": { "get": {
"security": [ "security": [
{ {
@ -87,15 +87,10 @@ func init() {
"operationId": "getEnvironmentDetail", "operationId": "getEnvironmentDetail",
"parameters": [ "parameters": [
{ {
"name": "body", "type": "string",
"in": "body", "name": "envZId",
"schema": { "in": "path",
"properties": { "required": true
"envZId": {
"type": "string"
}
}
}
} }
], ],
"responses": { "responses": {
@ -1209,7 +1204,7 @@ func init() {
} }
} }
}, },
"/detail/environment": { "/detail/environment/{envZId}": {
"get": { "get": {
"security": [ "security": [
{ {
@ -1222,15 +1217,10 @@ func init() {
"operationId": "getEnvironmentDetail", "operationId": "getEnvironmentDetail",
"parameters": [ "parameters": [
{ {
"name": "body", "type": "string",
"in": "body", "name": "envZId",
"schema": { "in": "path",
"properties": { "required": true
"envZId": {
"type": "string"
}
}
}
} }
], ],
"responses": { "responses": {

View File

@ -6,12 +6,9 @@ package metadata
// Editing this file might prove futile when you re-run the generate command // Editing this file might prove futile when you re-run the generate command
import ( import (
"context"
"net/http" "net/http"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
) )
@ -35,7 +32,7 @@ func NewGetEnvironmentDetail(ctx *middleware.Context, handler GetEnvironmentDeta
} }
/* /*
GetEnvironmentDetail swagger:route GET /detail/environment metadata getEnvironmentDetail GetEnvironmentDetail swagger:route GET /detail/environment/{envZId} metadata getEnvironmentDetail
GetEnvironmentDetail get environment detail API GetEnvironmentDetail get environment detail API
*/ */
@ -72,40 +69,3 @@ func (o *GetEnvironmentDetail) ServeHTTP(rw http.ResponseWriter, r *http.Request
o.Context.Respond(rw, r, route.Produces, route, res) o.Context.Respond(rw, r, route.Produces, route, res)
} }
// GetEnvironmentDetailBody get environment detail body
//
// swagger:model GetEnvironmentDetailBody
type GetEnvironmentDetailBody struct {
// env z Id
EnvZID string `json:"envZId,omitempty"`
}
// Validate validates this get environment detail body
func (o *GetEnvironmentDetailBody) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this get environment detail body based on context it is used
func (o *GetEnvironmentDetailBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *GetEnvironmentDetailBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *GetEnvironmentDetailBody) UnmarshalBinary(b []byte) error {
var res GetEnvironmentDetailBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}

View File

@ -9,9 +9,8 @@ import (
"net/http" "net/http"
"github.com/go-openapi/errors" "github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate" "github.com/go-openapi/strfmt"
) )
// NewGetEnvironmentDetailParams creates a new GetEnvironmentDetailParams object // NewGetEnvironmentDetailParams creates a new GetEnvironmentDetailParams object
@ -32,9 +31,10 @@ type GetEnvironmentDetailParams struct {
HTTPRequest *http.Request `json:"-"` HTTPRequest *http.Request `json:"-"`
/* /*
In: body Required: true
In: path
*/ */
Body GetEnvironmentDetailBody EnvZID string
} }
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@ -46,29 +46,26 @@ func (o *GetEnvironmentDetailParams) BindRequest(r *http.Request, route *middlew
o.HTTPRequest = r o.HTTPRequest = r
if runtime.HasBody(r) { rEnvZID, rhkEnvZID, _ := route.Params.GetOK("envZId")
defer r.Body.Close() if err := o.bindEnvZID(rEnvZID, rhkEnvZID, route.Formats); err != nil {
var body GetEnvironmentDetailBody
if err := route.Consumer.Consume(r.Body, &body); err != nil {
res = append(res, errors.NewParseError("body", "body", "", err))
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err) res = append(res, err)
} }
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = body
}
}
}
if len(res) > 0 { if len(res) > 0 {
return errors.CompositeValidationError(res...) return errors.CompositeValidationError(res...)
} }
return nil return nil
} }
// bindEnvZID binds and validates parameter EnvZID from path.
func (o *GetEnvironmentDetailParams) bindEnvZID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.EnvZID = raw
return nil
}

View File

@ -9,11 +9,16 @@ import (
"errors" "errors"
"net/url" "net/url"
golangswaggerpaths "path" golangswaggerpaths "path"
"strings"
) )
// GetEnvironmentDetailURL generates an URL for the get environment detail operation // GetEnvironmentDetailURL generates an URL for the get environment detail operation
type GetEnvironmentDetailURL struct { type GetEnvironmentDetailURL struct {
EnvZID string
_basePath string _basePath string
// avoid unkeyed usage
_ struct{}
} }
// WithBasePath sets the base path for this url builder, only required when it's different from the // WithBasePath sets the base path for this url builder, only required when it's different from the
@ -35,7 +40,14 @@ func (o *GetEnvironmentDetailURL) SetBasePath(bp string) {
func (o *GetEnvironmentDetailURL) Build() (*url.URL, error) { func (o *GetEnvironmentDetailURL) Build() (*url.URL, error) {
var _result url.URL var _result url.URL
var _path = "/detail/environment" var _path = "/detail/environment/{envZId}"
envZID := o.EnvZID
if envZID != "" {
_path = strings.Replace(_path, "{envZId}", envZID, -1)
} else {
return nil, errors.New("envZId is required on GetEnvironmentDetailURL")
}
_basePath := o._basePath _basePath := o._basePath
if _basePath == "" { if _basePath == "" {

View File

@ -466,7 +466,7 @@ func (o *ZrokAPI) initHandlerCache() {
if o.handlers["GET"] == nil { if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler) o.handlers["GET"] = make(map[string]http.Handler)
} }
o.handlers["GET"]["/detail/environment"] = metadata.NewGetEnvironmentDetail(o.context, o.MetadataGetEnvironmentDetailHandler) o.handlers["GET"]["/detail/environment/{envZId}"] = metadata.NewGetEnvironmentDetail(o.context, o.MetadataGetEnvironmentDetailHandler)
if o.handlers["GET"] == nil { if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler) o.handlers["GET"] = make(map[string]http.Handler)
} }

View File

@ -252,7 +252,7 @@ paths:
# #
# metadata # metadata
# #
/detail/environment: /detail/environment/{envZId}:
get: get:
tags: tags:
- metadata - metadata
@ -260,12 +260,10 @@ paths:
- key: [] - key: []
operationId: getEnvironmentDetail operationId: getEnvironmentDetail
parameters: parameters:
- name: body - name: envZId
in: body in: path
schema:
properties:
envZId:
type: string type: string
required: true
responses: responses:
200: 200:
description: ok description: ok

View File

@ -3,15 +3,13 @@
import * as gateway from './gateway' import * as gateway from './gateway'
/** /**
* @param {object} options Optional options * @param {string} envZId
* @param {object} [options.body]
* @return {Promise<module:types.environmentServices>} ok * @return {Promise<module:types.environmentServices>} ok
*/ */
export function getEnvironmentDetail(options) { export function getEnvironmentDetail(envZId) {
if (!options) options = {}
const parameters = { const parameters = {
body: { path: {
body: options.body envZId
} }
} }
return gateway.request(getEnvironmentDetailOperation, parameters) return gateway.request(getEnvironmentDetailOperation, parameters)
@ -30,8 +28,7 @@ export function version() {
} }
const getEnvironmentDetailOperation = { const getEnvironmentDetailOperation = {
path: '/detail/environment', path: '/detail/environment/{envZId}',
contentTypes: ['application/zrok.v1+json'],
method: 'get', method: 'get',
security: [ security: [
{ {

View File

@ -4,7 +4,7 @@ import Visualizer from "./visualizer/Visualizer";
import Enable from "./modals/Enable"; import Enable from "./modals/Enable";
import Version from "./modals/Version"; import Version from "./modals/Version";
import * as metadata from "../api/metadata"; import * as metadata from "../api/metadata";
import Detail from "./Detail"; import Detail from "./detail/Detail";
const Console = (props) => { const Console = (props) => {
const [showEnableModal, setShowEnableModal] = useState(false); const [showEnableModal, setShowEnableModal] = useState(false);
@ -34,7 +34,7 @@ const Console = (props) => {
setOverview(resp.data); setOverview(resp.data);
} }
}) })
}, 1000) }, 1000);
return () => { return () => {
mounted = false; mounted = false;
clearInterval(interval); clearInterval(interval);

View File

@ -1,9 +0,0 @@
const Detail = (props) => {
return (
<div className={"detail-container"}>
<h1>{props.selection.id} ({props.selection.type})</h1>
</div>
);
};
export default Detail;

View File

@ -0,0 +1,17 @@
import EnvironmentDetail from "./EnvironmentDetail";
const Detail = (props) => {
let detailComponent = <h1>{props.selection.id} ({props.selection.type})</h1>;
switch(props.selection.type) {
case "environment":
detailComponent = <EnvironmentDetail selection={props.selection} />;
break;
}
return (
<div className={"detail-container"}>{detailComponent}</div>
);
};
export default Detail;

View File

@ -0,0 +1,80 @@
import * as metadata from "../../api/metadata";
import {useEffect, useState} from "react";
import DataTable from 'react-data-table-component';
import {Sparklines, SparklinesLine, SparklinesSpots} from "react-sparklines";
const EnvironmentDetail = (props) => {
const [detail, setDetail] = useState({});
useEffect(() => {
metadata.getEnvironmentDetail(props.selection.id)
.then(resp => {
setDetail(resp.data);
});
}, [props.selection]);
useEffect(() => {
let mounted = true;
let interval = setInterval(() => {
if(mounted) {}
metadata.getEnvironmentDetail(props.selection.id)
.then(resp => {
if(mounted) {
setDetail(resp.data);
}
});
}, 1000);
return () => {
mounted = false;
clearInterval(interval);
}
}, [props.selection]);
const columns = [
{
name: "Frontend",
selector: row => <a href={row.frontendEndpoint} target={"_"}>{row.frontendEndpoint}</a>,
sortable: true
},
{
name: "Backend",
selector: row => row.backendProxyEndpoint,
sortable: true
},
{
name: "Share Mode",
selector: row => row.shareMode,
},
{
name: "Token",
selector: row => row.token,
sortable: true,
},
{
name: "Activity",
cell: row => {
console.log(row.metrics);
return <Sparklines data={row.metrics} height={20} limit={60}><SparklinesLine color={"#3b2693"}/><SparklinesSpots/></Sparklines>;
}
}
];
if(detail.environment) {
return (
<div>
<h2>Environment: {detail.environment.description} ({detail.environment.zId})</h2>
<div className={"zrok-datatable"}>
<DataTable
className={"zrok-datatable"}
data={detail.services}
columns={columns}
defaultSortField={1}
/>
</div>
</div>
);
}
return <></>;
}
export default EnvironmentDetail;

View File

@ -37,6 +37,10 @@ code, pre {
margin-top: 15px; margin-top: 15px;
} }
.zrok-datatable {
margin-top: 30px;
}
.btn-close { .btn-close {
background: transparent url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3E%3Cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3E%3C/svg%3E") 50%/1em auto no-repeat; background: transparent url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3E%3Cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3E%3C/svg%3E") 50%/1em auto no-repeat;
height: 25px; height: 25px;