mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2025-09-05 08:12:25 +08:00
fresh available
This commit is contained in:
parent
56397bc270
commit
866e4cd2fb
@ -16,6 +16,7 @@
|
|||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
"antd": "^4.16.13",
|
"antd": "^4.16.13",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
@ -51,6 +52,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.175",
|
"@types/lodash": "^4.14.175",
|
||||||
|
"@types/react-redux": "^7.1.20",
|
||||||
"@types/react-router-dom": "^5.3.0",
|
"@types/react-router-dom": "^5.3.0",
|
||||||
"react-app-rewired": "^2.1.8",
|
"react-app-rewired": "^2.1.8",
|
||||||
"redux-devtools": "^3.7.0"
|
"redux-devtools": "^3.7.0"
|
||||||
|
@ -1,45 +1,36 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
|
||||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
|
||||||
import './App.css';
|
|
||||||
import { LoginContext, loginContextDefault, GlobalConfContext } from './utils/context';
|
|
||||||
import { LoginStatus, GlobalConf, AllPlatformConf } from './utils/type';
|
|
||||||
import { Admin } from './pages/admin';
|
|
||||||
import { getGlobalConf } from './api/config';
|
|
||||||
import { Auth } from './pages/auth';
|
|
||||||
import 'antd/dist/antd.css';
|
import 'antd/dist/antd.css';
|
||||||
|
import React, {useEffect} from 'react';
|
||||||
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
|
||||||
|
import './App.css';
|
||||||
|
import {Admin} from './pages/admin';
|
||||||
|
import {Auth} from './pages/auth';
|
||||||
|
import {getGlobalConf} from './store/globalConfSlice';
|
||||||
|
import {useAppSelector} from './store/hooks';
|
||||||
|
import {loadLoginState, loginSelector} from './store/loginSlice';
|
||||||
|
|
||||||
|
|
||||||
function LoginSwitch() {
|
function LoginSwitch() {
|
||||||
const {login, save} = useContext(LoginContext);
|
const login = useSelector(loginSelector)
|
||||||
if (login.login) {
|
if (login.login) {
|
||||||
return <Admin />;
|
return <Admin />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
not login
|
not login
|
||||||
<button onClick={() => save({
|
|
||||||
login: true, type: 'admin', name: '', id: '123', token: ''
|
|
||||||
})}>1</button>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [loginStatus, setLogin] = useState(loginContextDefault.login);
|
const dispatch = useDispatch()
|
||||||
const [globalConf, setGlobalConf] = useState<GlobalConf>({platformConf: {} as AllPlatformConf, loaded: false});
|
const globalConf = useAppSelector(state => state.globalConf)
|
||||||
// const globalConfContext = useContext(GlobalConfContext);
|
|
||||||
const save = (login: LoginStatus) => setLogin(_ => login);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGlobalConf = async () => {
|
dispatch(getGlobalConf());
|
||||||
const res = await getGlobalConf();
|
dispatch(loadLoginState())
|
||||||
setGlobalConf(_ => {return {...res, loaded: true}});
|
}, [dispatch]);
|
||||||
};
|
return <>
|
||||||
fetchGlobalConf();
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<LoginContext.Provider value={{login: loginStatus, save}}>
|
|
||||||
<GlobalConfContext.Provider value={globalConf}>
|
|
||||||
{ globalConf.loaded &&
|
{ globalConf.loaded &&
|
||||||
<Router basename="/bison">
|
<Router basename="/bison">
|
||||||
<Switch>
|
<Switch>
|
||||||
@ -52,9 +43,7 @@ function App() {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
}
|
}
|
||||||
</GlobalConfContext.Provider>
|
</>;
|
||||||
</LoginContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -8,7 +8,7 @@ export const baseUrl = '/bison/api/'
|
|||||||
axios.interceptors.request.use(function (config) {
|
axios.interceptors.request.use(function (config) {
|
||||||
if (config.url && config.url.startsWith(baseUrl) && config.url !== `${baseUrl}auth`
|
if (config.url && config.url.startsWith(baseUrl) && config.url !== `${baseUrl}auth`
|
||||||
&& config.url !== `${baseUrl}global_conf`) {
|
&& config.url !== `${baseUrl}global_conf`) {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers['Authorization'] = `Bearer ${token}`;
|
config.headers['Authorization'] = `Bearer ${token}`;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {Form, Input, Modal, Select, Tag} from 'antd';
|
import {Form, Input, Modal, Select, Tag} from 'antd';
|
||||||
import React, {useContext, useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
import {addSubscribe, getTargetName, updateSubscribe} from 'src/api/config';
|
import {addSubscribe, getTargetName, updateSubscribe} from 'src/api/config';
|
||||||
import {InputTag} from 'src/component/inputTag';
|
import {InputTag} from 'src/component/inputTag';
|
||||||
import {GlobalConfContext} from "src/utils/context";
|
import {platformConfSelector} from 'src/store/globalConfSlice';
|
||||||
import {CategoryConfig, SubscribeConfig} from 'src/utils/type';
|
import {CategoryConfig, SubscribeConfig} from 'src/utils/type';
|
||||||
|
|
||||||
interface InputTagCustomProp {
|
interface InputTagCustomProp {
|
||||||
@ -49,7 +50,7 @@ export function AddModal({
|
|||||||
showModal, groupNumber, setShowModal, refresh, initVal
|
showModal, groupNumber, setShowModal, refresh, initVal
|
||||||
}: AddModalProp) {
|
}: AddModalProp) {
|
||||||
const [ confirmLoading, setConfirmLoading ] = useState<boolean>(false);
|
const [ confirmLoading, setConfirmLoading ] = useState<boolean>(false);
|
||||||
const { platformConf } = useContext(GlobalConfContext);
|
const platformConf = useSelector(platformConfSelector)
|
||||||
const [ hasTarget, setHasTarget ] = useState(false);
|
const [ hasTarget, setHasTarget ] = useState(false);
|
||||||
const [ categories, setCategories ] = useState({} as CategoryConfig);
|
const [ categories, setCategories ] = useState({} as CategoryConfig);
|
||||||
const [ enabledTag, setEnableTag ] = useState(false);
|
const [ enabledTag, setEnableTag ] = useState(false);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {CopyOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons';
|
import {CopyOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons';
|
||||||
import {Card, Col, Form, message, Popconfirm, Select, Tag, Tooltip} from 'antd';
|
import {Card, Col, Form, message, Popconfirm, Select, Tag, Tooltip} from 'antd';
|
||||||
import Modal from 'antd/lib/modal/Modal';
|
import Modal from 'antd/lib/modal/Modal';
|
||||||
import React, {useContext, useState} from "react";
|
import React, {useState} from "react";
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
import {addSubscribe, delSubscribe} from 'src/api/config';
|
import {addSubscribe, delSubscribe} from 'src/api/config';
|
||||||
import {GlobalConfContext} from "src/utils/context";
|
import {platformConfSelector} from 'src/store/globalConfSlice';
|
||||||
import {PlatformConfig, SubscribeConfig, SubscribeResp} from 'src/utils/type';
|
import {PlatformConfig, SubscribeConfig, SubscribeResp} from 'src/utils/type';
|
||||||
import {AddModal} from './addSubsModal';
|
import {AddModal} from './addSubsModal';
|
||||||
|
|
||||||
@ -57,10 +58,11 @@ interface SubscribeCardProp {
|
|||||||
reload: () => void
|
reload: () => void
|
||||||
}
|
}
|
||||||
export function SubscribeCard({groupNumber, config, reload, groupSubscribes}: SubscribeCardProp) {
|
export function SubscribeCard({groupNumber, config, reload, groupSubscribes}: SubscribeCardProp) {
|
||||||
const globalConf = useContext(GlobalConfContext);
|
// const globalConf = useSelector()
|
||||||
|
const platformConfs = useSelector(platformConfSelector)
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [showEditModal, setShowEditModal] = useState(false)
|
const [showEditModal, setShowEditModal] = useState(false)
|
||||||
const platformConf = globalConf.platformConf[config.platformName] as PlatformConfig;
|
const platformConf = platformConfs[config.platformName] as PlatformConfig;
|
||||||
const handleDelete = (groupNumber: string, platformName: string, target: string) => () => {
|
const handleDelete = (groupNumber: string, platformName: string, target: string) => () => {
|
||||||
delSubscribe(groupNumber, platformName, target).then(() => {
|
delSubscribe(groupNumber, platformName, target).then(() => {
|
||||||
reload()
|
reload()
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './index.css';
|
import {Provider} from 'react-redux';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import './index.css';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
import store from './store';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import {BugOutlined, SettingOutlined} from '@ant-design/icons';
|
import {BugOutlined, SettingOutlined} from '@ant-design/icons';
|
||||||
import {Layout, Menu} from 'antd';
|
import {Layout, Menu} from 'antd';
|
||||||
import React, {useContext, useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {LoginContext} from "src/utils/context";
|
import {useSelector} from 'react-redux';
|
||||||
|
import {loginSelector} from 'src/store/loginSlice';
|
||||||
import './admin.css';
|
import './admin.css';
|
||||||
import {ConfigPage} from './configPage';
|
import {ConfigPage} from './configPage';
|
||||||
|
|
||||||
export function Admin() {
|
export function Admin() {
|
||||||
const { login } = useContext(LoginContext);
|
const login = useSelector(loginSelector)
|
||||||
const [ tab, changeTab ] = useState("manage");
|
const [ tab, changeTab ] = useState("manage");
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh' }}>
|
<Layout style={{ minHeight: '100vh' }}>
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
import React, {useContext, useEffect, useState} from "react";
|
import React, {useEffect} from "react";
|
||||||
import { useParams } from "react-router";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import { auth } from '../api/config';
|
import {useParams} from "react-router";
|
||||||
import { LoginContext } from '../utils/context';
|
import {Redirect} from 'react-router-dom';
|
||||||
import { Redirect } from 'react-router-dom'
|
import {login, loginSelector} from 'src/store/loginSlice';
|
||||||
|
|
||||||
interface AuthParam {
|
interface AuthParam {
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
export function Auth() {
|
export function Auth() {
|
||||||
const { code } = useParams<AuthParam>();
|
const { code } = useParams<AuthParam>();
|
||||||
const [ content, contentUpdate ] = useState(<div>Logining...</div>);
|
const dispatch = useDispatch();
|
||||||
const { save } = useContext(LoginContext);
|
const loginState = useSelector(loginSelector)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loginFun = async () => {
|
const loginFun = async () => {
|
||||||
const resp = await auth(code);
|
dispatch(login(code));
|
||||||
if (resp.status === 200) {
|
|
||||||
save({login: true, type: resp.type, name: resp.name, id: resp.id, token: resp.token});
|
|
||||||
contentUpdate(_ => <Redirect to={{pathname: '/admin'}} />);
|
|
||||||
sessionStorage.setItem('token', resp.token);
|
|
||||||
} else {
|
|
||||||
contentUpdate(_ => <div>登录失败,请重新获取连接</div>);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
loginFun();
|
loginFun();
|
||||||
}, [code, save])
|
}, [code, dispatch])
|
||||||
return content;
|
return <>
|
||||||
|
{ loginState.login ?
|
||||||
|
<Redirect to={{pathname: '/admin'}} /> :
|
||||||
|
loginState.failed ?
|
||||||
|
<div>登录失败,请重新获取连接</div> :
|
||||||
|
<div>Logining...</div>
|
||||||
|
}
|
||||||
|
</>;
|
||||||
}
|
}
|
||||||
|
35
admin-frontend/src/store/globalConfSlice.ts
Normal file
35
admin-frontend/src/store/globalConfSlice.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {CaseReducer, createAsyncThunk, createSlice, PayloadAction} from "@reduxjs/toolkit";
|
||||||
|
import {getGlobalConf as getGlobalConfApi} from "src/api/config";
|
||||||
|
import {GlobalConf} from "src/utils/type";
|
||||||
|
import {RootState} from ".";
|
||||||
|
|
||||||
|
|
||||||
|
const initialState: GlobalConf = {
|
||||||
|
platformConf: {},
|
||||||
|
loaded: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const setGlobalConf: CaseReducer<GlobalConf, PayloadAction<GlobalConf>> = (_, action) => {
|
||||||
|
return {...action.payload, loaded: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getGlobalConf = createAsyncThunk(
|
||||||
|
"globalConf/set",
|
||||||
|
getGlobalConfApi,
|
||||||
|
{
|
||||||
|
condition: (_, { getState }) => !(getState() as RootState).globalConf.loaded
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const globalConfSlice = createSlice({
|
||||||
|
name: "globalConf",
|
||||||
|
initialState,
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(getGlobalConf.fulfilled, setGlobalConf)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const platformConfSelector = (state: RootState) => state.globalConf.platformConf
|
||||||
|
|
||||||
|
export default globalConfSlice.reducer
|
5
admin-frontend/src/store/hooks.ts
Normal file
5
admin-frontend/src/store/hooks.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux";
|
||||||
|
import {AppDispatch, RootState} from ".";
|
||||||
|
|
||||||
|
export const useAppDispacher = () => useDispatch<AppDispatch>()
|
||||||
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
15
admin-frontend/src/store/index.ts
Normal file
15
admin-frontend/src/store/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {configureStore} from "@reduxjs/toolkit";
|
||||||
|
import loginSlice from "./loginSlice";
|
||||||
|
import globalConfSlice from "./globalConfSlice";
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
login: loginSlice,
|
||||||
|
globalConf: globalConfSlice,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store;
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
99
admin-frontend/src/store/loginSlice.ts
Normal file
99
admin-frontend/src/store/loginSlice.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { AnyAction, CaseReducer, createAsyncThunk, createSlice, PayloadAction, ThunkAction } from "@reduxjs/toolkit";
|
||||||
|
import jwt_decode from 'jwt-decode';
|
||||||
|
import { LoginStatus, TokenResp } from "src/utils/type";
|
||||||
|
import { auth } from "src/api/config";
|
||||||
|
import {RootState} from ".";
|
||||||
|
|
||||||
|
const initialState: LoginStatus = {
|
||||||
|
login: false,
|
||||||
|
type: '',
|
||||||
|
name: '',
|
||||||
|
id: '123',
|
||||||
|
// groups: [],
|
||||||
|
token: '',
|
||||||
|
failed: false
|
||||||
|
}
|
||||||
|
|
||||||
|
interface storedInfo {
|
||||||
|
type: string
|
||||||
|
name: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginAction: CaseReducer<LoginStatus, PayloadAction<TokenResp>> = (_, action) => {
|
||||||
|
return {
|
||||||
|
login: true,
|
||||||
|
failed: false,
|
||||||
|
type: action.payload.type,
|
||||||
|
name: action.payload.name,
|
||||||
|
id: action.payload.id,
|
||||||
|
token: action.payload.token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const login = createAsyncThunk(
|
||||||
|
"auth/login",
|
||||||
|
async (code: string) => {
|
||||||
|
let res = await auth(code);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw Error("Login Error")
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('loginInfo', JSON.stringify({
|
||||||
|
'type': res.type,
|
||||||
|
'name': res.name,
|
||||||
|
id: res.id,
|
||||||
|
}))
|
||||||
|
localStorage.setItem('token', res.token)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: (_: string, { getState }) => {
|
||||||
|
const { login } = getState() as { login: LoginStatus }
|
||||||
|
return !login.login;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
export const loginSlice = createSlice({
|
||||||
|
name: 'auth',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
doLogin: loginAction
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(login.fulfilled, loginAction);
|
||||||
|
builder.addCase(login.rejected, (stat) => {
|
||||||
|
stat.failed = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { doLogin } = loginSlice.actions
|
||||||
|
|
||||||
|
export const loadLoginState = (): ThunkAction<void, RootState, unknown, AnyAction> =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
if (getState().login.login) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const infoJson = localStorage.getItem('loginInfo')
|
||||||
|
const jwtToken = localStorage.getItem('token');
|
||||||
|
if (infoJson && jwtToken) {
|
||||||
|
const decodedJwt = jwt_decode(jwtToken) as { exp: number };
|
||||||
|
if (decodedJwt.exp < Date.now() / 1000) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const info = JSON.parse(infoJson) as storedInfo
|
||||||
|
const payload: TokenResp = {
|
||||||
|
...info,
|
||||||
|
status: 200,
|
||||||
|
token: jwtToken,
|
||||||
|
}
|
||||||
|
dispatch(doLogin(payload))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loginSelector = (state: RootState) => state.login
|
||||||
|
|
||||||
|
export default loginSlice.reducer
|
@ -1,17 +0,0 @@
|
|||||||
import { createContext } from "react";
|
|
||||||
import { LoginContextType, GlobalConf } from "./type";
|
|
||||||
|
|
||||||
export const loginContextDefault: LoginContextType = {
|
|
||||||
login: {
|
|
||||||
login: false,
|
|
||||||
type: '',
|
|
||||||
name: '',
|
|
||||||
id: '123',
|
|
||||||
// groups: [],
|
|
||||||
token: ''
|
|
||||||
},
|
|
||||||
save: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LoginContext = createContext(loginContextDefault);
|
|
||||||
export const GlobalConfContext = createContext<GlobalConf>({platformConf: {}, loaded: false});
|
|
@ -9,7 +9,8 @@ export interface LoginStatus {
|
|||||||
name: string
|
name: string
|
||||||
id: string
|
id: string
|
||||||
// groups: Array<QQGroup>
|
// groups: Array<QQGroup>
|
||||||
token: string
|
token: string,
|
||||||
|
failed: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LoginContextType = {
|
export type LoginContextType = {
|
||||||
|
@ -7138,6 +7138,11 @@ jsonfile@^6.0.1:
|
|||||||
array-includes "^3.1.3"
|
array-includes "^3.1.3"
|
||||||
object.assign "^4.1.2"
|
object.assign "^4.1.2"
|
||||||
|
|
||||||
|
jwt-decode@^3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.npm.taobao.org/jwt-decode/download/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
||||||
|
integrity sha1-P7MZ82daLfDCiVyPXp+ktnsE7Vk=
|
||||||
|
|
||||||
killable@^1.0.1:
|
killable@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.nlark.com/killable/download/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
|
resolved "https://registry.nlark.com/killable/download/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user