mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-24 23:34:45 +00:00
Feature/UI/filters (#32)
* UI filters
* refactor
* Revert "refactor"
This reverts commit 70e7d4b6ac
.
* remove recursive func
This commit is contained in:
parent
b9d0e0ee87
commit
c7a20ed9c0
@ -1,8 +1,9 @@
|
||||
import {HarEntry} from "./HarEntry";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||
import styles from './style/HarEntriesList.module.sass';
|
||||
import spinner from './assets/spinner.svg';
|
||||
import ScrollableFeed from "react-scrollable-feed";
|
||||
import {StatusType} from "./HarFilters";
|
||||
|
||||
interface HarEntriesListProps {
|
||||
entries: any[];
|
||||
@ -14,6 +15,9 @@ interface HarEntriesListProps {
|
||||
setNoMoreDataTop: (flag: boolean) => void;
|
||||
noMoreDataBottom: boolean;
|
||||
setNoMoreDataBottom: (flag: boolean) => void;
|
||||
methodsFilter: Array<string>;
|
||||
statusFilter: Array<string>;
|
||||
pathFilter: string
|
||||
}
|
||||
|
||||
enum FetchOperator {
|
||||
@ -21,91 +25,107 @@ enum FetchOperator {
|
||||
GT = "gt"
|
||||
}
|
||||
|
||||
export const HarEntriesList: React.FC<HarEntriesListProps> = ({entries, setEntries, focusedEntryId, setFocusedEntryId, connectionOpen, noMoreDataTop, setNoMoreDataTop, noMoreDataBottom, setNoMoreDataBottom}) => {
|
||||
export const HarEntriesList: React.FC<HarEntriesListProps> = ({entries, setEntries, focusedEntryId, setFocusedEntryId, connectionOpen, noMoreDataTop, setNoMoreDataTop, noMoreDataBottom, setNoMoreDataBottom, methodsFilter, statusFilter, pathFilter}) => {
|
||||
|
||||
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
||||
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(loadMoreTop && !connectionOpen && !noMoreDataTop)
|
||||
fetchData(FetchOperator.LT);
|
||||
}, [loadMoreTop, connectionOpen, noMoreDataTop]);
|
||||
|
||||
useEffect(() => {
|
||||
const list = document.getElementById('list').firstElementChild;
|
||||
list.addEventListener('scroll', (e) => {
|
||||
const el: any = e.target;
|
||||
if(el.scrollTop === 0) {
|
||||
setLoadMoreTop(true);
|
||||
} else {
|
||||
setLoadMoreTop(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const fetchData = async (operator) => {
|
||||
const filterEntries = useCallback((entry) => {
|
||||
if(methodsFilter.length > 0 && !methodsFilter.includes(entry.method.toLowerCase())) return;
|
||||
if(pathFilter && entry.path?.toLowerCase()?.indexOf(pathFilter) === -1) return;
|
||||
if(statusFilter.includes(StatusType.SUCCESS) && entry.statusCode >= 400) return;
|
||||
if(statusFilter.includes(StatusType.ERROR) && entry.statusCode < 400) return;
|
||||
return entry;
|
||||
},[methodsFilter, pathFilter, statusFilter])
|
||||
|
||||
const timestamp = operator === FetchOperator.LT ? entries[0].timestamp : entries[entries.length - 1].timestamp;
|
||||
if(operator === FetchOperator.LT)
|
||||
setIsLoadingTop(true);
|
||||
const filteredEntries = useMemo(() => {
|
||||
return entries.filter(filterEntries);
|
||||
},[entries, filterEntries])
|
||||
|
||||
fetch(`http://localhost:8899/api/entries?limit=50&operator=${operator}×tamp=${timestamp}`)
|
||||
.then(response => response.json())
|
||||
.then((data: any[]) => {
|
||||
let scrollTo;
|
||||
if(operator === FetchOperator.LT) {
|
||||
if(data.length === 0) {
|
||||
setNoMoreDataTop(true);
|
||||
scrollTo = document.getElementById("noMoreDataTop");
|
||||
} else {
|
||||
scrollTo = document.getElementById(entries[0].id);
|
||||
}
|
||||
const newEntries = [...data, ...entries];
|
||||
if(newEntries.length >= 1000) {
|
||||
newEntries.splice(1000);
|
||||
}
|
||||
setEntries(newEntries);
|
||||
setLoadMoreTop(false);
|
||||
setIsLoadingTop(false)
|
||||
if(scrollTo) {
|
||||
scrollTo.scrollIntoView();
|
||||
}
|
||||
}
|
||||
const fetchData = async (operator, timestamp) => {
|
||||
const response = await fetch(`http://localhost:8899/api/entries?limit=50&operator=${operator}×tamp=${timestamp}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
if(operator === FetchOperator.GT) {
|
||||
if(data.length === 0) {
|
||||
setNoMoreDataBottom(true);
|
||||
}
|
||||
scrollTo = document.getElementById(entries[entries.length -1].id);
|
||||
let newEntries = [...entries, ...data];
|
||||
if(newEntries.length >= 1000) {
|
||||
setNoMoreDataTop(false);
|
||||
newEntries = newEntries.slice(-1000);
|
||||
}
|
||||
setEntries(newEntries);
|
||||
if(scrollTo) {
|
||||
scrollTo.scrollIntoView({behavior: "smooth"});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
const getOldEntries = useCallback(async () => {
|
||||
setIsLoadingTop(true);
|
||||
const data = await fetchData(FetchOperator.LT, entries[0].timestamp);
|
||||
setLoadMoreTop(false);
|
||||
|
||||
let scrollTo;
|
||||
if(data.length === 0) {
|
||||
setNoMoreDataTop(true);
|
||||
scrollTo = document.getElementById("noMoreDataTop");
|
||||
} else {
|
||||
scrollTo = document.getElementById(filteredEntries?.[0]?.id);
|
||||
}
|
||||
setIsLoadingTop(false);
|
||||
const newEntries = [...data, ...entries];
|
||||
if(newEntries.length >= 1000) {
|
||||
newEntries.splice(1000);
|
||||
}
|
||||
setEntries(newEntries);
|
||||
|
||||
if(scrollTo) {
|
||||
scrollTo.scrollIntoView();
|
||||
}
|
||||
},[setLoadMoreTop, setIsLoadingTop, entries, setEntries, filteredEntries, setNoMoreDataTop])
|
||||
|
||||
useEffect(() => {
|
||||
if(!loadMoreTop || connectionOpen || noMoreDataTop) return;
|
||||
getOldEntries();
|
||||
}, [loadMoreTop, connectionOpen, noMoreDataTop, getOldEntries]);
|
||||
|
||||
const getNewEntries = async () => {
|
||||
const data = await fetchData(FetchOperator.GT, entries[entries.length - 1].timestamp);
|
||||
let scrollTo;
|
||||
if(data.length === 0) {
|
||||
setNoMoreDataBottom(true);
|
||||
}
|
||||
scrollTo = document.getElementById(filteredEntries?.[filteredEntries.length -1]?.id);
|
||||
let newEntries = [...entries, ...data];
|
||||
if(newEntries.length >= 1000) {
|
||||
setNoMoreDataTop(false);
|
||||
newEntries = newEntries.slice(-1000);
|
||||
}
|
||||
setEntries(newEntries);
|
||||
if(scrollTo) {
|
||||
scrollTo.scrollIntoView({behavior: "smooth"});
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={styles.list}>
|
||||
<div id="list" className={styles.list}>
|
||||
{isLoadingTop && <div style={{display: "flex", justifyContent: "center", marginBottom: 10}}><img alt="spinner" src={spinner} style={{height: 25}}/></div>}
|
||||
{isLoadingTop && <div className={styles.spinnerContainer}>
|
||||
<img alt="spinner" src={spinner} style={{height: 25}}/>
|
||||
</div>}
|
||||
<ScrollableFeed>
|
||||
{noMoreDataTop && !connectionOpen && <div id="noMoreDataTop" style={{textAlign: "center", fontWeight: 600, color: "rgba(255,255,255,0.75)"}}>No more data available</div>}
|
||||
{entries?.map(entry => <HarEntry key={entry.id}
|
||||
{noMoreDataTop && !connectionOpen && <div id="noMoreDataTop" className={styles.noMoreDataAvailable}>No more data available</div>}
|
||||
{filteredEntries.map(entry => <HarEntry key={entry.id}
|
||||
entry={entry}
|
||||
setFocusedEntryId={setFocusedEntryId}
|
||||
isSelected={focusedEntryId === entry.id}/>)}
|
||||
{!connectionOpen && !noMoreDataBottom && <div style={{width: "100%", display: "flex", justifyContent: "center", marginTop: 12, fontWeight: 600, color: "rgba(255,255,255,0.75)"}}>
|
||||
<div className={styles.styledButton} onClick={() => fetchData(FetchOperator.GT)}>Fetch more entries</div>
|
||||
{!connectionOpen && !noMoreDataBottom && <div className={styles.fetchButtonContainer}>
|
||||
<div className={styles.styledButton} onClick={() => getNewEntries()}>Fetch more entries</div>
|
||||
</div>}
|
||||
</ScrollableFeed>
|
||||
</div>
|
||||
|
||||
{entries?.length > 0 && <div className={styles.footer}>
|
||||
<div><b>{entries?.length}</b> requests</div>
|
||||
<div><b>{filteredEntries?.length !== entries.length && `${filteredEntries?.length} / `} {entries?.length}</b> requests</div>
|
||||
<div>Started listening at <span style={{marginRight: 5, fontWeight: 600, fontSize: 13}}>{new Date(+entries[0].timestamp)?.toLocaleString()}</span></div>
|
||||
</div>}
|
||||
</div>
|
||||
|
@ -1,17 +1,24 @@
|
||||
import React, {useEffect} from "react";
|
||||
import React from "react";
|
||||
import styles from './style/HarFilters.module.sass';
|
||||
import {HARFilterSelect} from "./HARFilterSelect";
|
||||
import {TextField} from "@material-ui/core";
|
||||
import {ALL_KEY} from "./Select";
|
||||
|
||||
export const HarFilters: React.FC = () => {
|
||||
interface HarFiltersProps {
|
||||
methodsFilter: Array<string>;
|
||||
setMethodsFilter: (methods: Array<string>) => void;
|
||||
statusFilter: Array<string>;
|
||||
setStatusFilter: (methods: Array<string>) => void;
|
||||
pathFilter: string
|
||||
setPathFilter: (val: string) => void;
|
||||
}
|
||||
|
||||
export const HarFilters: React.FC<HarFiltersProps> = ({methodsFilter, setMethodsFilter, statusFilter, setStatusFilter, pathFilter, setPathFilter}) => {
|
||||
|
||||
return <div className={styles.container}>
|
||||
<ServiceFilter/>
|
||||
<MethodFilter/>
|
||||
<StatusTypesFilter/>
|
||||
<SourcesFilter/>
|
||||
<FetchModeFilter/>
|
||||
<PathFilter/>
|
||||
<MethodFilter methodsFilter={methodsFilter} setMethodsFilter={setMethodsFilter}/>
|
||||
<StatusTypesFilter statusFilter={statusFilter} setStatusFilter={setStatusFilter}/>
|
||||
<PathFilter pathFilter={pathFilter} setPathFilter={setPathFilter}/>
|
||||
</div>;
|
||||
};
|
||||
|
||||
@ -23,77 +30,6 @@ const FilterContainer: React.FC = ({children}) => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const ServiceFilter: React.FC = () => {
|
||||
const providerIds = []; //todo
|
||||
const selectedServices = []; //todo
|
||||
|
||||
return <FilterContainer>
|
||||
<HARFilterSelect
|
||||
items={providerIds}
|
||||
value={selectedServices}
|
||||
onChange={(val) => {
|
||||
//todo: harStore.updateFilter({toggleService: val})
|
||||
}}
|
||||
allowMultiple={true}
|
||||
label={"Services"}
|
||||
transformDisplay={_toUpperCase}
|
||||
/>
|
||||
</FilterContainer>
|
||||
|
||||
};
|
||||
|
||||
const BROWSER_SOURCE = "_BROWSER_";
|
||||
|
||||
const SourcesFilter: React.FC = () => {
|
||||
|
||||
const sources = []; //todo
|
||||
const selectedSource = null; //todo
|
||||
|
||||
useEffect(() => {
|
||||
//todo: fetch sources
|
||||
}, []);
|
||||
|
||||
return <FilterContainer>
|
||||
<HARFilterSelect
|
||||
items={sources}
|
||||
value={selectedSource}
|
||||
onChange={(val) => {
|
||||
//todo: harStore.updateFilter({toggleSource: val});
|
||||
}}
|
||||
allowMultiple={true}
|
||||
label={"Sources"}
|
||||
transformDisplay={item => item === BROWSER_SOURCE ? "BROWSER" : item.toUpperCase()}
|
||||
/>
|
||||
</FilterContainer>
|
||||
|
||||
};
|
||||
|
||||
enum HARFetchMode {
|
||||
UP_TO_REVISION = "Up to revision",
|
||||
ALL = "All",
|
||||
QUEUED = "Unprocessed"
|
||||
}
|
||||
|
||||
const FetchModeFilter: React.FC = () => {
|
||||
|
||||
const selectedHarFetchMode = null;
|
||||
|
||||
return <FilterContainer>
|
||||
<HARFilterSelect
|
||||
items={Object.values(HARFetchMode)}
|
||||
value={selectedHarFetchMode}
|
||||
onChange={(val) => {
|
||||
// selectedModelStore.har.setHarFetchMode(val);
|
||||
// selectedModelStore.har.data.reset();
|
||||
// selectedModelStore.har.data.fetch();
|
||||
//todo
|
||||
}}
|
||||
label={"Processed"}
|
||||
/>
|
||||
</FilterContainer>
|
||||
|
||||
};
|
||||
|
||||
enum HTTPMethod {
|
||||
GET = "get",
|
||||
PUT = "put",
|
||||
@ -103,58 +39,80 @@ enum HTTPMethod {
|
||||
PATCH = "patch"
|
||||
}
|
||||
|
||||
const MethodFilter: React.FC = () => {
|
||||
interface MethodFilterProps {
|
||||
methodsFilter: Array<string>;
|
||||
setMethodsFilter: (methods: Array<string>) => void;
|
||||
}
|
||||
|
||||
const selectedMethods = [];
|
||||
const MethodFilter: React.FC<MethodFilterProps> = ({methodsFilter, setMethodsFilter}) => {
|
||||
|
||||
const methodClicked = (val) => {
|
||||
if(val === ALL_KEY) {
|
||||
setMethodsFilter([]);
|
||||
return;
|
||||
}
|
||||
if(methodsFilter.includes(val)) {
|
||||
setMethodsFilter(methodsFilter.filter(method => method !== val))
|
||||
} else {
|
||||
setMethodsFilter([...methodsFilter, val]);
|
||||
}
|
||||
}
|
||||
|
||||
return <FilterContainer>
|
||||
<HARFilterSelect
|
||||
items={Object.values(HTTPMethod)}
|
||||
allowMultiple={true}
|
||||
value={selectedMethods}
|
||||
onChange={(val) => {
|
||||
// harStore.updateFilter({toggleMethod: val}) todo
|
||||
}}
|
||||
value={methodsFilter}
|
||||
onChange={(val) => methodClicked(val)}
|
||||
transformDisplay={_toUpperCase}
|
||||
label={"Methods"}
|
||||
/>
|
||||
</FilterContainer>;
|
||||
};
|
||||
|
||||
enum StatusType {
|
||||
export enum StatusType {
|
||||
SUCCESS = "success",
|
||||
ERROR = "error"
|
||||
}
|
||||
|
||||
const StatusTypesFilter: React.FC = () => {
|
||||
interface StatusTypesFilterProps {
|
||||
statusFilter: Array<string>;
|
||||
setStatusFilter: (methods: Array<string>) => void;
|
||||
}
|
||||
|
||||
const selectedStatusTypes = [];
|
||||
const StatusTypesFilter: React.FC<StatusTypesFilterProps> = ({statusFilter, setStatusFilter}) => {
|
||||
|
||||
const statusClicked = (val) => {
|
||||
if(val === ALL_KEY) {
|
||||
setStatusFilter([]);
|
||||
return;
|
||||
}
|
||||
setStatusFilter([val]);
|
||||
}
|
||||
|
||||
return <FilterContainer>
|
||||
<HARFilterSelect
|
||||
items={Object.values(StatusType)}
|
||||
allowMultiple={true}
|
||||
value={selectedStatusTypes}
|
||||
onChange={(val) => {
|
||||
// harStore.updateFilter({toggleStatusType: val}) todo
|
||||
}}
|
||||
value={statusFilter}
|
||||
onChange={(val) => statusClicked(val)}
|
||||
transformDisplay={_toUpperCase}
|
||||
label="Status"
|
||||
/>
|
||||
</FilterContainer>;
|
||||
};
|
||||
|
||||
// TODO path search is inclusive of the qs -> we want to avoid this - TRA-1681
|
||||
const PathFilter: React.FC = () => {
|
||||
interface PathFilterProps {
|
||||
pathFilter: string;
|
||||
setPathFilter: (val: string) => void;
|
||||
}
|
||||
|
||||
const onFilterChange = (value) => {
|
||||
// harStore.updateFilter({setPathSearch: value}); todo
|
||||
}
|
||||
const PathFilter: React.FC<PathFilterProps> = ({pathFilter, setPathFilter}) => {
|
||||
|
||||
return <FilterContainer>
|
||||
<div className={styles.filterLabel}>Path</div>
|
||||
<div>
|
||||
<TextField variant="outlined" className={styles.filterText} style={{minWidth: '150px'}} onKeyDown={(e: any) => e.key === "Enter" && onFilterChange(e.target.value)}/>
|
||||
<TextField value={pathFilter} variant="outlined" className={styles.filterText} style={{minWidth: '150px'}} onChange={(e: any) => setPathFilter(e.target.value)}/>
|
||||
</div>
|
||||
</FilterContainer>;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
// import {HarFilters} from "./HarFilters";
|
||||
import {HarFilters} from "./HarFilters";
|
||||
import {HarEntriesList} from "./HarEntriesList";
|
||||
import {makeStyles} from "@material-ui/core";
|
||||
import "./style/HarPage.sass";
|
||||
@ -43,6 +43,10 @@ export const HarPage: React.FC = () => {
|
||||
const [noMoreDataTop, setNoMoreDataTop] = useState(false);
|
||||
const [noMoreDataBottom, setNoMoreDataBottom] = useState(false);
|
||||
|
||||
const [methodsFilter, setMethodsFilter] = useState([]);
|
||||
const [statusFilter, setStatusFilter] = useState([]);
|
||||
const [pathFilter, setPathFilter] = useState("");
|
||||
|
||||
const ws = useRef(null);
|
||||
|
||||
const openWebSocket = () => {
|
||||
@ -53,7 +57,6 @@ export const HarPage: React.FC = () => {
|
||||
|
||||
if(ws.current) {
|
||||
ws.current.onmessage = e => {
|
||||
console.log(connection);
|
||||
if(!e?.data) return;
|
||||
const entry = JSON.parse(e.data);
|
||||
if(connection === ConnectionStatus.Paused) {
|
||||
@ -98,18 +101,35 @@ export const HarPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getConnectionTitle = () => {
|
||||
switch (connection) {
|
||||
case ConnectionStatus.Paused:
|
||||
return "traffic paused";
|
||||
case ConnectionStatus.Connected:
|
||||
return "connected, waiting for traffic"
|
||||
default:
|
||||
return "not connected";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="HarPage">
|
||||
<div style={{padding: "0 24px 24px 24px", display: "flex", alignItems: "center"}}>
|
||||
<img style={{cursor: "pointer", marginRight: 15, height: 20}} alt="pause" src={connection === ConnectionStatus.Connected ? pauseIcon : playIcon} onClick={toggleConnection}/>
|
||||
<div className="connectionText">
|
||||
{connection === ConnectionStatus.Connected ? "connected, waiting for traffic" : "not connected"}
|
||||
{getConnectionTitle()}
|
||||
<div className={getConnectionStatusClass()}/>
|
||||
</div>
|
||||
</div>
|
||||
{entries.length > 0 && <div className="HarPage-Container">
|
||||
<div className="HarPage-ListContainer">
|
||||
{/*<HarFilters />*/}
|
||||
<HarFilters methodsFilter={methodsFilter}
|
||||
setMethodsFilter={setMethodsFilter}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
pathFilter={pathFilter}
|
||||
setPathFilter={setPathFilter}
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
<HarEntriesList entries={entries}
|
||||
setEntries={setEntries}
|
||||
@ -119,7 +139,11 @@ export const HarPage: React.FC = () => {
|
||||
noMoreDataBottom={noMoreDataBottom}
|
||||
setNoMoreDataBottom={setNoMoreDataBottom}
|
||||
noMoreDataTop={noMoreDataTop}
|
||||
setNoMoreDataTop={setNoMoreDataTop}/>
|
||||
setNoMoreDataTop={setNoMoreDataTop}
|
||||
methodsFilter={methodsFilter}
|
||||
statusFilter={statusFilter}
|
||||
pathFilter={pathFilter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.details}>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {ReactComponent as DefaultIconDown} from '../../../assets/default_icon_down.svg';
|
||||
import {ReactComponent as DefaultIconDown} from './assets/default_icon_down.svg';
|
||||
import {MenuItem, Select as MUISelect} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import {SelectProps as MUISelectProps} from '@material-ui/core/Select/Select';
|
||||
import styles from './style/Select.module.sass';
|
||||
|
||||
const ALL_KEY= 'All';
|
||||
export const ALL_KEY= 'All';
|
||||
|
||||
const menuProps: any = {
|
||||
anchorOrigin: {
|
||||
|
3
ui/src/components/assets/default_icon_down.svg
Normal file
3
ui/src/components/assets/default_icon_down.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="7.237" height="7.237" viewBox="0 0 7.237 7.237" fill="white">
|
||||
<path id="icon_down" d="M5.117 0H3.07v3.07H0v2.047h5.117V0z" transform="rotate(45 1.809 4.367)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 218 B |
3
ui/src/components/style/HARFilterSelect.module.sass
Normal file
3
ui/src/components/style/HARFilterSelect.module.sass
Normal file
@ -0,0 +1,3 @@
|
||||
.HARSelectLabel
|
||||
color: #8f9bb2
|
||||
font-size: 11px
|
@ -35,4 +35,22 @@
|
||||
|
||||
.styledButton:hover
|
||||
border: 1px solid #627ef7
|
||||
background-color: rgba(255, 255, 255, 0.06)
|
||||
background-color: rgba(255, 255, 255, 0.06)
|
||||
|
||||
.spinnerContainer
|
||||
display: flex
|
||||
justify-content: center
|
||||
margin-bottom: 10px
|
||||
|
||||
.noMoreDataAvailable
|
||||
text-align: center
|
||||
font-weight: 600
|
||||
color: rgba(255,255,255,0.75)
|
||||
|
||||
.fetchButtonContainer
|
||||
width: 100%
|
||||
display: flex
|
||||
justify-content: center
|
||||
margin-top: 12px
|
||||
font-weight: 600
|
||||
color: rgba(255,255,255,0.75)
|
@ -16,7 +16,8 @@
|
||||
border: solid 1px lighten(#4253a5, 20%)
|
||||
|
||||
.rowSelected
|
||||
border: solid 1px #4253a5
|
||||
background: #293053
|
||||
border: 1px #ffffff61 solid
|
||||
|
||||
.service
|
||||
text-overflow: ellipsis
|
||||
|
@ -27,5 +27,6 @@
|
||||
background: #171922
|
||||
border-radius: 12px
|
||||
font-size: 12px
|
||||
color: white
|
||||
fieldset
|
||||
border: none
|
||||
|
@ -16,6 +16,124 @@ body
|
||||
code
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace
|
||||
|
||||
.uppercase
|
||||
text-transform: uppercase
|
||||
|
||||
/****
|
||||
* Button
|
||||
***/
|
||||
button
|
||||
span
|
||||
line-height: 1
|
||||
&:not(.MuiFab-root)
|
||||
&.MuiButtonBase-root
|
||||
box-sizing: border-box
|
||||
font-weight: 500
|
||||
line-height: 1
|
||||
border-radius: 20px
|
||||
letter-spacing: 0.02857em
|
||||
text-transform: uppercase
|
||||
img:not(.custom)
|
||||
max-width: 13px
|
||||
max-height: 13px
|
||||
|
||||
&.tiny
|
||||
min-width: 0
|
||||
|
||||
/****
|
||||
* Select
|
||||
***/
|
||||
.select
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: flex-start
|
||||
color: white
|
||||
.MuiInput-underline
|
||||
&::before,
|
||||
&::after
|
||||
display: none
|
||||
content: ''
|
||||
|
||||
.MuiSelect-root
|
||||
&.MuiSelect-select
|
||||
border-radius: 20px
|
||||
cursor: pointer
|
||||
min-width: 2rem
|
||||
font-weight: normal
|
||||
border: solid 0
|
||||
padding: 3px 16px 4px 12px
|
||||
|
||||
.MuiInputBase-input
|
||||
border-radius: 20px
|
||||
background-color: rgba(255, 255, 255, 0.06)
|
||||
cursor: pointer
|
||||
padding-top: 0
|
||||
padding-bottom: 0
|
||||
font-size: 12px
|
||||
font-weight: normal
|
||||
font-stretch: normal
|
||||
font-style: normal
|
||||
letter-spacing: normal
|
||||
text-align: left
|
||||
line-height: 1.25
|
||||
min-height: initial
|
||||
&:focus
|
||||
background-color: rgba(255, 255, 255, 0.06) !important
|
||||
.MuiSelect-icon
|
||||
top: 50%
|
||||
transform: translateY(-50%)
|
||||
right: 5px
|
||||
position: absolute
|
||||
pointer-events: none
|
||||
&.MuiSelect-iconOpen
|
||||
transform: translateY(-50%) rotate(180deg)
|
||||
|
||||
.ellipsis
|
||||
display: block
|
||||
overflow: hidden
|
||||
white-space: nowrap
|
||||
width: 100px
|
||||
text-overflow: ellipsis
|
||||
color: white
|
||||
|
||||
.selectLabel
|
||||
margin-right: 8px
|
||||
|
||||
&.labelOnTop
|
||||
flex-direction: column
|
||||
align-items: flex-start
|
||||
.selectLabel
|
||||
margin-right: 0
|
||||
margin-bottom: 4px
|
||||
|
||||
/****
|
||||
* Paper/List/Menu list
|
||||
***/
|
||||
.MuiPaper-root
|
||||
background-color: #344073 !important
|
||||
&.MuiPaper-rounded
|
||||
border-radius: 4px
|
||||
|
||||
.MuiList-root
|
||||
padding: 0
|
||||
&.MuiMenu-list
|
||||
border-radius: 4px
|
||||
box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.5)
|
||||
color: #a0b2ff
|
||||
.MuiListItem-root
|
||||
&.MuiMenuItem-root
|
||||
padding: 14px
|
||||
font-size: 11px
|
||||
font-weight: 600
|
||||
font-stretch: normal
|
||||
font-style: normal
|
||||
line-height: 1.25
|
||||
&:not(:last-child)
|
||||
border-bottom: 1px solid rgb(53, 65, 114)
|
||||
&.Mui-selected
|
||||
background-color: #3f519a6e
|
||||
color: white
|
||||
|
||||
// scroll-bar css
|
||||
|
||||
::-webkit-scrollbar
|
||||
|
Loading…
Reference in New Issue
Block a user