| @@ -0,0 +1,94 @@ | |||
| package controllers | |||
| import ( | |||
| "encoding/json" | |||
| "github.com/casbin/casibase/object" | |||
| ) | |||
| func (c *ApiController) GetGlobalProviders() { | |||
| providers, err := object.GetGlobalProviders() | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| c.ResponseOk(providers) | |||
| } | |||
| func (c *ApiController) GetProviders() { | |||
| owner := c.Input().Get("owner") | |||
| providers, err := object.GetProviders(owner) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| c.ResponseOk(providers) | |||
| } | |||
| func (c *ApiController) GetProvider() { | |||
| id := c.Input().Get("id") | |||
| provider, err := object.GetProvider(id) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| c.ResponseOk(provider) | |||
| } | |||
| func (c *ApiController) UpdateProvider() { | |||
| id := c.Input().Get("id") | |||
| var provider object.Provider | |||
| err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| success, err := object.UpdateProvider(id, &provider) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| c.ResponseOk(success) | |||
| } | |||
| func (c *ApiController) AddProvider() { | |||
| var provider object.Provider | |||
| err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| success, err := object.AddProvider(&provider) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| c.ResponseOk(success) | |||
| } | |||
| func (c *ApiController) DeleteProvider() { | |||
| var provider object.Provider | |||
| err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| success, err := object.DeleteProvider(&provider) | |||
| if err != nil { | |||
| c.ResponseError(err.Error()) | |||
| return | |||
| } | |||
| c.ResponseOk(success) | |||
| } | |||
| @@ -108,4 +108,9 @@ func (a *Adapter) createTable() { | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| err = a.engine.Sync2(new(Provider)) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| @@ -0,0 +1,101 @@ | |||
| package object | |||
| import ( | |||
| "fmt" | |||
| "github.com/casbin/casibase/util" | |||
| "xorm.io/core" | |||
| ) | |||
| type Provider struct { | |||
| Owner string `xorm:"varchar(100) notnull pk" json:"owner"` | |||
| Name string `xorm:"varchar(100) notnull pk" json:"name"` | |||
| CreatedTime string `xorm:"varchar(100)" json:"createdTime"` | |||
| DisplayName string `xorm:"varchar(100)" json:"displayName"` | |||
| Category string `xorm:"varchar(100)" json:"category"` | |||
| Type string `xorm:"varchar(100)" json:"type"` | |||
| ClientId string `xorm:"varchar(100)" json:"clientId"` | |||
| ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` | |||
| ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"` | |||
| } | |||
| func GetGlobalProviders() ([]*Provider, error) { | |||
| providers := []*Provider{} | |||
| err := adapter.engine.Asc("owner").Desc("created_time").Find(&providers) | |||
| if err != nil { | |||
| return providers, err | |||
| } | |||
| return providers, nil | |||
| } | |||
| func GetProviders(owner string) ([]*Provider, error) { | |||
| providers := []*Provider{} | |||
| err := adapter.engine.Desc("created_time").Find(&providers, &Provider{Owner: owner}) | |||
| if err != nil { | |||
| return providers, err | |||
| } | |||
| return providers, nil | |||
| } | |||
| func getProvider(owner string, name string) (*Provider, error) { | |||
| provider := Provider{Owner: owner, Name: name} | |||
| existed, err := adapter.engine.Get(&provider) | |||
| if err != nil { | |||
| return &provider, err | |||
| } | |||
| if existed { | |||
| return &provider, nil | |||
| } else { | |||
| return nil, nil | |||
| } | |||
| } | |||
| func GetProvider(id string) (*Provider, error) { | |||
| owner, name := util.GetOwnerAndNameFromId(id) | |||
| return getProvider(owner, name) | |||
| } | |||
| func UpdateProvider(id string, provider *Provider) (bool, error) { | |||
| owner, name := util.GetOwnerAndNameFromId(id) | |||
| _, err := getProvider(owner, name) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| if provider == nil { | |||
| return false, nil | |||
| } | |||
| _, err = adapter.engine.ID(core.PK{owner, name}).AllCols().Update(provider) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| //return affected != 0 | |||
| return true, nil | |||
| } | |||
| func AddProvider(provider *Provider) (bool, error) { | |||
| affected, err := adapter.engine.Insert(provider) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| return affected != 0, nil | |||
| } | |||
| func DeleteProvider(provider *Provider) (bool, error) { | |||
| affected, err := adapter.engine.ID(core.PK{provider.Owner, provider.Name}).Delete(&Provider{}) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| return affected != 0, nil | |||
| } | |||
| func (provider *Provider) GetId() string { | |||
| return fmt.Sprintf("%s/%s", provider.Owner, provider.Name) | |||
| } | |||
| @@ -53,6 +53,13 @@ func initAPI() { | |||
| beego.Router("/api/add-store", &controllers.ApiController{}, "POST:AddStore") | |||
| beego.Router("/api/delete-store", &controllers.ApiController{}, "POST:DeleteStore") | |||
| beego.Router("/api/get-global-providers", &controllers.ApiController{}, "GET:GetGlobalProviders") | |||
| beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders") | |||
| beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider") | |||
| beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider") | |||
| beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider") | |||
| beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider") | |||
| beego.Router("/api/update-file", &controllers.ApiController{}, "POST:UpdateFile") | |||
| beego.Router("/api/add-file", &controllers.ApiController{}, "POST:AddFile") | |||
| beego.Router("/api/delete-file", &controllers.ApiController{}, "POST:DeleteFile") | |||
| @@ -19,6 +19,8 @@ import VectorsetListPage from "./VectorsetListPage"; | |||
| import VectorsetEditPage from "./VectorsetEditPage"; | |||
| import VideoListPage from "./VideoListPage"; | |||
| import VideoEditPage from "./VideoEditPage"; | |||
| import ProviderListPage from "./ProviderListPage"; | |||
| import ProviderEditPage from "./ProviderEditPage"; | |||
| import SigninPage from "./SigninPage"; | |||
| import i18next from "i18next"; | |||
| import LanguageSelect from "./LanguageSelect"; | |||
| @@ -70,6 +72,8 @@ class App extends Component { | |||
| this.setState({selectedMenuKey: "/vectorsets"}); | |||
| } else if (uri.includes("/videos")) { | |||
| this.setState({selectedMenuKey: "/videos"}); | |||
| } else if (uri.includes("/providers")) { | |||
| this.setState({selectedMenuKey: "/providers"}); | |||
| } else { | |||
| this.setState({selectedMenuKey: "null"}); | |||
| } | |||
| @@ -289,6 +293,13 @@ class App extends Component { | |||
| </Link> | |||
| </Menu.Item> | |||
| ); | |||
| res.push( | |||
| <Menu.Item key="/providers"> | |||
| <Link to="/providers"> | |||
| {i18next.t("general:Providers")} | |||
| </Link> | |||
| </Menu.Item> | |||
| ); | |||
| if (Setting.isLocalAdminUser(this.state.account)) { | |||
| res.push( | |||
| @@ -363,6 +374,8 @@ class App extends Component { | |||
| <Route exact path="/vectorsets/:vectorsetName" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetEditPage account={this.state.account} {...props} />)} /> | |||
| <Route exact path="/videos" render={(props) => this.renderSigninIfNotSignedIn(<VideoListPage account={this.state.account} {...props} />)} /> | |||
| <Route exact path="/videos/:videoName" render={(props) => this.renderSigninIfNotSignedIn(<VideoEditPage account={this.state.account} {...props} />)} /> | |||
| <Route exact path="/providers" render={(props) => this.renderSigninIfNotSignedIn(<ProviderListPage account={this.state.account} {...props} />)} /> | |||
| <Route exact path="/providers/:providerName" render={(props) => this.renderSigninIfNotSignedIn(<ProviderEditPage account={this.state.account} {...props} />)} /> | |||
| </Switch> | |||
| </div> | |||
| ); | |||
| @@ -0,0 +1,185 @@ | |||
| import React from "react"; | |||
| import {Button, Card, Col, Input, Row, Select} from "antd"; | |||
| import * as ProviderBackend from "./backend/ProviderBackend"; | |||
| import * as Setting from "./Setting"; | |||
| import i18next from "i18next"; | |||
| import {LinkOutlined} from "@ant-design/icons"; | |||
| const {Option} = Select; | |||
| class ProviderEditPage extends React.Component { | |||
| constructor(props) { | |||
| super(props); | |||
| this.state = { | |||
| classes: props, | |||
| providerName: props.match.params.providerName, | |||
| provider: null, | |||
| }; | |||
| } | |||
| UNSAFE_componentWillMount() { | |||
| this.getProvider(); | |||
| } | |||
| getProvider() { | |||
| ProviderBackend.getProvider(this.props.account.name, this.state.providerName) | |||
| .then((provider) => { | |||
| if (provider.status === "ok") { | |||
| this.setState({ | |||
| provider: provider.data, | |||
| }); | |||
| } else { | |||
| Setting.showMessage("error", `Failed to get provider: ${provider.msg}`); | |||
| } | |||
| }); | |||
| } | |||
| parseProviderField(key, value) { | |||
| if ([""].includes(key)) { | |||
| value = Setting.myParseInt(value); | |||
| } | |||
| return value; | |||
| } | |||
| updateProviderField(key, value) { | |||
| value = this.parseProviderField(key, value); | |||
| const provider = this.state.provider; | |||
| provider[key] = value; | |||
| this.setState({ | |||
| provider: provider, | |||
| }); | |||
| } | |||
| renderProvider() { | |||
| return ( | |||
| <Card size="small" title={ | |||
| <div> | |||
| {i18next.t("provider:Edit Provider")} | |||
| <Button type="primary" onClick={this.submitProviderEdit.bind(this)}>{i18next.t("general:Save")}</Button> | |||
| </div> | |||
| } style={{marginLeft: "5px"}} type="inner"> | |||
| <Row style={{marginTop: "10px"}} > | |||
| <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> | |||
| {i18next.t("general:Name")}: | |||
| </Col> | |||
| <Col span={22} > | |||
| <Input value={this.state.provider.name} onChange={e => { | |||
| this.updateProviderField("name", e.target.value); | |||
| }} /> | |||
| </Col> | |||
| </Row> | |||
| <Row style={{marginTop: "20px"}} > | |||
| <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> | |||
| {i18next.t("general:Display name")}: | |||
| </Col> | |||
| <Col span={22} > | |||
| <Input value={this.state.provider.displayName} onChange={e => { | |||
| this.updateProviderField("displayName", e.target.value); | |||
| }} /> | |||
| </Col> | |||
| </Row> | |||
| <Row style={{marginTop: "20px"}} > | |||
| <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> | |||
| {i18next.t("provider:Category")}: | |||
| </Col> | |||
| <Col span={22} > | |||
| <Select virtual={false} style={{width: "100%"}} value={this.state.provider.category} onChange={(value => {this.updateProviderField("category", value);})}> | |||
| { | |||
| [ | |||
| {id: "AI", name: "AI"}, | |||
| ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) | |||
| } | |||
| </Select> | |||
| </Col> | |||
| </Row> | |||
| <Row style={{marginTop: "20px"}} > | |||
| <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> | |||
| {i18next.t("provider:Type")}: | |||
| </Col> | |||
| <Col span={22} > | |||
| <Select virtual={false} style={{width: "100%"}} value={this.state.provider.type} onChange={(value => {this.updateProviderField("type", value);})}> | |||
| { | |||
| [ | |||
| {id: "OpenAI API - GPT 3.5", name: "OpenAI API - GPT 3.5"}, | |||
| {id: "OpenAI API - GPT 4", name: "OpenAI API - GPT 4"}, | |||
| ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) | |||
| } | |||
| </Select> | |||
| </Col> | |||
| </Row> | |||
| <Row style={{marginTop: "20px"}} > | |||
| <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> | |||
| {i18next.t("provider:Secret key")}: | |||
| </Col> | |||
| <Col span={22} > | |||
| <Input value={this.state.provider.clientSecret} onChange={e => { | |||
| this.updateProviderField("clientSecret", e.target.value); | |||
| }} /> | |||
| </Col> | |||
| </Row> | |||
| <Row style={{marginTop: "20px"}} > | |||
| <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> | |||
| {i18next.t("general:Provider URL")}: | |||
| </Col> | |||
| <Col span={22} > | |||
| <Input prefix={<LinkOutlined />} value={this.state.provider.providerUrl} onChange={e => { | |||
| this.updateProviderField("providerUrl", e.target.value); | |||
| }} /> | |||
| </Col> | |||
| </Row> | |||
| </Card> | |||
| ); | |||
| } | |||
| submitProviderEdit() { | |||
| const provider = Setting.deepCopy(this.state.provider); | |||
| ProviderBackend.updateProvider(this.state.provider.owner, this.state.providerName, provider) | |||
| .then((res) => { | |||
| if (res.status === "ok") { | |||
| if (res.data) { | |||
| Setting.showMessage("success", "Successfully saved"); | |||
| this.setState({ | |||
| providerName: this.state.provider.name, | |||
| }); | |||
| this.props.history.push(`/providers/${this.state.provider.name}`); | |||
| } else { | |||
| Setting.showMessage("error", "failed to save: server side failure"); | |||
| this.updateProviderField("name", this.state.providerName); | |||
| } | |||
| } else { | |||
| Setting.showMessage("error", `failed to save: ${res.msg}`); | |||
| } | |||
| }) | |||
| .catch(error => { | |||
| Setting.showMessage("error", `failed to save: ${error}`); | |||
| }); | |||
| } | |||
| render() { | |||
| return ( | |||
| <div> | |||
| <Row style={{width: "100%"}}> | |||
| <Col span={1}> | |||
| </Col> | |||
| <Col span={22}> | |||
| { | |||
| this.state.provider !== null ? this.renderProvider() : null | |||
| } | |||
| </Col> | |||
| <Col span={1}> | |||
| </Col> | |||
| </Row> | |||
| <Row style={{margin: 10}}> | |||
| <Col span={2}> | |||
| </Col> | |||
| <Col span={18}> | |||
| <Button type="primary" size="large" onClick={this.submitProviderEdit.bind(this)}>{i18next.t("general:Save")}</Button> | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| export default ProviderEditPage; | |||
| @@ -0,0 +1,202 @@ | |||
| import React from "react"; | |||
| import {Link} from "react-router-dom"; | |||
| import {Button, Col, Popconfirm, Row, Table} from "antd"; | |||
| import moment from "moment"; | |||
| import * as Setting from "./Setting"; | |||
| import * as ProviderBackend from "./backend/ProviderBackend"; | |||
| import i18next from "i18next"; | |||
| class ProviderListPage extends React.Component { | |||
| constructor(props) { | |||
| super(props); | |||
| this.state = { | |||
| classes: props, | |||
| providers: null, | |||
| }; | |||
| } | |||
| UNSAFE_componentWillMount() { | |||
| this.getProviders(); | |||
| } | |||
| getProviders() { | |||
| ProviderBackend.getProviders(this.props.account.name) | |||
| .then((res) => { | |||
| if (res.status === "ok") { | |||
| this.setState({ | |||
| providers: res.data, | |||
| }); | |||
| } else { | |||
| Setting.showMessage("error", `Failed to get providers: ${res.msg}`); | |||
| } | |||
| }); | |||
| } | |||
| newProvider() { | |||
| const randomName = Setting.getRandomName(); | |||
| return { | |||
| owner: this.props.account.name, | |||
| name: `provider_${randomName}`, | |||
| createdTime: moment().format(), | |||
| displayName: `New Provider - ${randomName}`, | |||
| category: "AI", | |||
| type: "OpenAI API - GPT 3.5", | |||
| clientId: "", | |||
| clientSecret: "", | |||
| providerUrl: "https://platform.openai.com/account/api-keys", | |||
| }; | |||
| } | |||
| addProvider() { | |||
| const newProvider = this.newProvider(); | |||
| ProviderBackend.addProvider(newProvider) | |||
| .then((res) => { | |||
| if (res.status === "ok") { | |||
| Setting.showMessage("success", "Provider added successfully"); | |||
| this.setState({ | |||
| providers: Setting.prependRow(this.state.providers, newProvider), | |||
| }); | |||
| } else { | |||
| Setting.showMessage("error", `Failed to add provider: ${res.msg}`); | |||
| } | |||
| }) | |||
| .catch(error => { | |||
| Setting.showMessage("error", `Provider failed to add: ${error}`); | |||
| }); | |||
| } | |||
| deleteProvider(i) { | |||
| ProviderBackend.deleteProvider(this.state.providers[i]) | |||
| .then((res) => { | |||
| if (res.status === "ok") { | |||
| Setting.showMessage("success", "Provider deleted successfully"); | |||
| this.setState({ | |||
| providers: Setting.deleteRow(this.state.providers, i), | |||
| }); | |||
| } else { | |||
| Setting.showMessage("error", `Provider failed to delete: ${res.msg}`); | |||
| } | |||
| }) | |||
| .catch(error => { | |||
| Setting.showMessage("error", `Provider failed to delete: ${error}`); | |||
| }); | |||
| } | |||
| renderTable(providers) { | |||
| const columns = [ | |||
| { | |||
| title: i18next.t("general:Name"), | |||
| dataIndex: "name", | |||
| key: "name", | |||
| width: "140px", | |||
| sorter: (a, b) => a.name.localeCompare(b.name), | |||
| render: (text, record, index) => { | |||
| return ( | |||
| <Link to={`/providers/${text}`}> | |||
| {text} | |||
| </Link> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: i18next.t("general:Display name"), | |||
| dataIndex: "displayName", | |||
| key: "displayName", | |||
| width: "200px", | |||
| sorter: (a, b) => a.displayName.localeCompare(b.displayName), | |||
| }, | |||
| { | |||
| title: i18next.t("provider:Category"), | |||
| dataIndex: "category", | |||
| key: "category", | |||
| width: "200px", | |||
| sorter: (a, b) => a.category.localeCompare(b.category), | |||
| }, | |||
| { | |||
| title: i18next.t("provider:Type"), | |||
| dataIndex: "type", | |||
| key: "type", | |||
| width: "200px", | |||
| sorter: (a, b) => a.type.localeCompare(b.type), | |||
| }, | |||
| { | |||
| title: i18next.t("provider:Secret key"), | |||
| dataIndex: "clientSecret", | |||
| key: "clientSecret", | |||
| width: "200px", | |||
| sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret), | |||
| }, | |||
| { | |||
| title: i18next.t("provider:Provider URL"), | |||
| dataIndex: "providerUrl", | |||
| key: "providerUrl", | |||
| width: "250px", | |||
| sorter: (a, b) => a.providerUrl.localeCompare(b.providerUrl), | |||
| render: (text, record, index) => { | |||
| return ( | |||
| <a target="_blank" rel="noreferrer" href={text}> | |||
| { | |||
| Setting.getShortText(text) | |||
| } | |||
| </a> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: i18next.t("general:Action"), | |||
| dataIndex: "action", | |||
| key: "action", | |||
| width: "180px", | |||
| render: (text, record, index) => { | |||
| return ( | |||
| <div> | |||
| <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>{i18next.t("general:Edit")}</Button> | |||
| <Popconfirm | |||
| title={`Sure to delete provider: ${record.name} ?`} | |||
| onConfirm={() => this.deleteProvider(index)} | |||
| okText="OK" | |||
| cancelText="Cancel" | |||
| > | |||
| <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button> | |||
| </Popconfirm> | |||
| </div> | |||
| ); | |||
| }, | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div> | |||
| <Table columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} | |||
| title={() => ( | |||
| <div> | |||
| {i18next.t("general:Providers")} | |||
| <Button type="primary" size="small" onClick={this.addProvider.bind(this)}>{i18next.t("general:Add")}</Button> | |||
| </div> | |||
| )} | |||
| loading={providers === null} | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| render() { | |||
| return ( | |||
| <div> | |||
| <Row style={{width: "100%"}}> | |||
| <Col span={1}> | |||
| </Col> | |||
| <Col span={22}> | |||
| { | |||
| this.renderTable(this.state.providers) | |||
| } | |||
| </Col> | |||
| <Col span={1}> | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| export default ProviderListPage; | |||
| @@ -0,0 +1,56 @@ | |||
| import * as Setting from "../Setting"; | |||
| export function getGlobalProviders() { | |||
| return fetch(`${Setting.ServerUrl}/api/get-global-providers`, { | |||
| method: "GET", | |||
| credentials: "include", | |||
| }).then(res => res.json()); | |||
| } | |||
| export function getProviders(owner) { | |||
| return fetch(`${Setting.ServerUrl}/api/get-providers?owner=${owner}`, { | |||
| method: "GET", | |||
| credentials: "include", | |||
| }).then(res => res.json()); | |||
| } | |||
| export function getProvider(owner, name) { | |||
| return fetch(`${Setting.ServerUrl}/api/get-provider?id=${owner}/${encodeURIComponent(name)}`, { | |||
| method: "GET", | |||
| credentials: "include", | |||
| }).then(res => res.json()); | |||
| } | |||
| export function getProviderGraph(owner, name, clusterNumber, distanceLimit) { | |||
| return fetch(`${Setting.ServerUrl}/api/get-provider-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, { | |||
| method: "GET", | |||
| credentials: "include", | |||
| }).then(res => res.json()); | |||
| } | |||
| export function updateProvider(owner, name, provider) { | |||
| const newProvider = Setting.deepCopy(provider); | |||
| return fetch(`${Setting.ServerUrl}/api/update-provider?id=${owner}/${encodeURIComponent(name)}`, { | |||
| method: "POST", | |||
| credentials: "include", | |||
| body: JSON.stringify(newProvider), | |||
| }).then(res => res.json()); | |||
| } | |||
| export function addProvider(provider) { | |||
| const newProvider = Setting.deepCopy(provider); | |||
| return fetch(`${Setting.ServerUrl}/api/add-provider`, { | |||
| method: "POST", | |||
| credentials: "include", | |||
| body: JSON.stringify(newProvider), | |||
| }).then(res => res.json()); | |||
| } | |||
| export function deleteProvider(provider) { | |||
| const newProvider = Setting.deepCopy(provider); | |||
| return fetch(`${Setting.ServerUrl}/api/delete-provider`, { | |||
| method: "POST", | |||
| credentials: "include", | |||
| body: JSON.stringify(newProvider), | |||
| }).then(res => res.json()); | |||
| } | |||