mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 19:38:09 +00:00
Add a simple log viewer inside the application.
This commit is contained in:
parent
cc9099752f
commit
fd8747e4aa
24 changed files with 280 additions and 90 deletions
|
|
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
- Video conference.
|
||||
- Log viewer.
|
||||
- Option to set the display name in "using an account" tab of assistant.
|
||||
|
||||
### Fixed
|
||||
- Crash on exit.
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Klik her: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Klicken Sie hier: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>Keine Plugins zu laden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Click here: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>No Plugins to load</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation>VIEW</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Haga clic aquí: <a href="%1">%1 </a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Cliquez ici : <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>Pas de plugin à charger</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2011,6 +2011,10 @@ Kattintson ide: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>Nincsenek betölthető beépülő modulok</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Clicca: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2011,6 +2011,10 @@
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2035,6 +2035,10 @@ Spustelėkite čia: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Clique aqui: <a href="%1">%1 </a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>Nenhum plug-in para carregar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2035,6 +2035,10 @@
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>Нет плагинов для загрузки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,10 @@ Klicka här: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2011,6 +2011,10 @@ Buraya tıklayın: <a href="%1">%1</a>
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>Yüklenecek eklenti yok</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2035,6 +2035,10 @@
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -2011,6 +2011,10 @@
|
|||
<extracomment>'No Plugins to load' : Text in combobox</extracomment>
|
||||
<translation>没有插件可加载</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>viewlogs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsAudio</name>
|
||||
|
|
|
|||
|
|
@ -209,3 +209,19 @@ void Logger::init (const shared_ptr<linphone::Config> &config) {
|
|||
|
||||
mInstance->enable(SettingsModel::getLogsEnabled(config));
|
||||
}
|
||||
|
||||
QString Logger::getLogText()const{
|
||||
QDir path = QString::fromStdString(linphone::Core::getLogCollectionPath());
|
||||
QString prefix = QString::fromStdString(linphone::Core::getLogCollectionPrefix());
|
||||
auto files = path.entryInfoList(QStringList(prefix+"*.log"), QDir::Files | QDir::NoSymLinks | QDir::Readable, QDir::Time);
|
||||
QString result;
|
||||
for(auto fileInfo : files){
|
||||
QFile file(fileInfo.filePath());
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QByteArray arr = file.readAll();
|
||||
result += QString::fromLatin1(arr);
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,9 @@ public:
|
|||
}
|
||||
|
||||
void enable (bool status);
|
||||
QString getLogText()const;
|
||||
|
||||
static void init (const std::shared_ptr<linphone::Config> &config);
|
||||
static void init (const std::shared_ptr<linphone::Config> &config);
|
||||
|
||||
static Logger *getInstance () {
|
||||
return mInstance;
|
||||
|
|
|
|||
|
|
@ -1462,6 +1462,10 @@ void SettingsModel::accessAdvancedSettings() {
|
|||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
QString SettingsModel::getLogText()const{
|
||||
return Logger::getInstance()->getLogText();
|
||||
}
|
||||
|
||||
QString SettingsModel::getLogsFolder () const {
|
||||
return getLogsFolder(mConfig);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -543,6 +543,8 @@ public:
|
|||
|
||||
void accessAdvancedSettings();
|
||||
|
||||
Q_INVOKABLE QString getLogText()const;
|
||||
|
||||
QString getLogsFolder () const;
|
||||
void setLogsFolder (const QString &folder);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ import 'SettingsAdvanced.js' as Logic
|
|||
// =============================================================================
|
||||
|
||||
TabContainer {
|
||||
id: mainItem
|
||||
color: "#00000000"
|
||||
signal showLogs()
|
||||
Column {
|
||||
id: column
|
||||
spacing: SettingsWindowStyle.forms.spacing
|
||||
|
|
@ -72,6 +74,14 @@ TabContainer {
|
|||
anchors.right: parent.right
|
||||
spacing: SettingsAdvancedStyle.buttons.spacing
|
||||
|
||||
TextButtonB {
|
||||
text: qsTr('viewlogs')
|
||||
|
||||
onClicked: {
|
||||
mainItem.showLogs()
|
||||
}
|
||||
}
|
||||
|
||||
TextButtonB {
|
||||
text: qsTr('cleanLogs')
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick 2.7
|
|||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import Clipboard 1.0
|
||||
import Common 1.0
|
||||
import Common.Styles 1.0
|
||||
import Konami 1.0
|
||||
|
|
@ -45,107 +46,173 @@ ApplicationWindow {
|
|||
// -------------------------------------------------------------------------
|
||||
// Navigation bar.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
RowLayout {
|
||||
Item{
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
TabBar {
|
||||
id: tabBar
|
||||
|
||||
onCurrentIndexChanged: SettingsModel.onSettingsTabChanged(currentIndex)
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.sipAccountsIcon
|
||||
text: qsTr('sipAccountsTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.audioIcon
|
||||
text: qsTr('audioTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
enabled: SettingsModel.videoSupported
|
||||
iconName: TabButtonStyle.icon.videoIcon
|
||||
text: qsTr('videoTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.callIcon
|
||||
text: qsTr('callsAndChatTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
enabled: SettingsModel.showNetworkSettings || SettingsModel.developerSettingsEnabled
|
||||
iconName: TabButtonStyle.icon.networkIcon
|
||||
text: qsTr('networkTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
visible: SettingsModel.tunnelAvailable()
|
||||
enabled: visible
|
||||
iconName: TabButtonStyle.icon.sipAccountsIcon
|
||||
//: 'Tunnel' : Tab title for tunnel section in settings.
|
||||
text: qsTr('tunnelTab')
|
||||
width: visible ? implicitWidth : 0
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.advancedIcon
|
||||
text: qsTr('uiTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.advancedIcon
|
||||
text: qsTr('uiAdvanced')
|
||||
width: implicitWidth
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: TabButtonStyle.text.height
|
||||
|
||||
color: TabButtonStyle.backgroundColor.normal
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
Layout.preferredHeight: TabButtonStyle.text.height
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
TabBar {
|
||||
id: tabBar
|
||||
|
||||
onClicked: konami.forceActiveFocus()
|
||||
cursorShape: Qt.ArrowCursor
|
||||
onCurrentIndexChanged: SettingsModel.onSettingsTabChanged(currentIndex)
|
||||
|
||||
Konami {
|
||||
id: konami
|
||||
onTriggered: SettingsModel.developerSettingsEnabled = true
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.sipAccountsIcon
|
||||
text: qsTr('sipAccountsTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.audioIcon
|
||||
text: qsTr('audioTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
enabled: SettingsModel.videoSupported
|
||||
iconName: TabButtonStyle.icon.videoIcon
|
||||
text: qsTr('videoTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.callIcon
|
||||
text: qsTr('callsAndChatTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
enabled: SettingsModel.showNetworkSettings || SettingsModel.developerSettingsEnabled
|
||||
iconName: TabButtonStyle.icon.networkIcon
|
||||
text: qsTr('networkTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
visible: SettingsModel.tunnelAvailable()
|
||||
enabled: visible
|
||||
iconName: TabButtonStyle.icon.sipAccountsIcon
|
||||
//: 'Tunnel' : Tab title for tunnel section in settings.
|
||||
text: qsTr('tunnelTab')
|
||||
width: visible ? implicitWidth : 0
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.advancedIcon
|
||||
text: qsTr('uiTab')
|
||||
width: implicitWidth
|
||||
}
|
||||
|
||||
TabButton {
|
||||
iconName: TabButtonStyle.icon.advancedIcon
|
||||
text: qsTr('uiAdvanced')
|
||||
width: implicitWidth
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: TabButtonStyle.text.height
|
||||
|
||||
color: TabButtonStyle.backgroundColor.normal
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: konami.forceActiveFocus()
|
||||
cursorShape: Qt.ArrowCursor
|
||||
|
||||
Konami {
|
||||
id: konami
|
||||
onTriggered: SettingsModel.developerSettingsEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle{
|
||||
id: hideBar
|
||||
anchors.fill: parent
|
||||
color: TabButtonStyle.backgroundColor.normal
|
||||
visible: logViewer.active
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Content.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
StackLayout {
|
||||
Item{
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
currentIndex: tabBar.currentIndex
|
||||
SettingsSipAccounts {}
|
||||
SettingsAudio {}
|
||||
SettingsVideo {}
|
||||
SettingsCallsChat {}
|
||||
SettingsNetwork {}
|
||||
SettingsTunnel {}
|
||||
SettingsUi {}
|
||||
SettingsAdvanced {}
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
currentIndex: tabBar.currentIndex
|
||||
SettingsSipAccounts {}
|
||||
SettingsAudio {}
|
||||
SettingsVideo {}
|
||||
SettingsCallsChat {}
|
||||
SettingsNetwork {}
|
||||
SettingsTunnel {}
|
||||
SettingsUi {}
|
||||
SettingsAdvanced {onShowLogs: logViewer.active=true }
|
||||
}
|
||||
Loader{
|
||||
id: logViewer
|
||||
anchors.fill: parent
|
||||
active: false
|
||||
sourceComponent: Component{
|
||||
Rectangle{
|
||||
id: logBackground
|
||||
anchors.fill: parent
|
||||
property variant stringList: null
|
||||
function updateText() {
|
||||
stringList = SettingsModel.getLogText().split('\n')
|
||||
idContentListView.positionViewAtEnd()
|
||||
}
|
||||
Component.onCompleted: updateText()
|
||||
ColumnLayout{
|
||||
anchors.fill: parent
|
||||
RowLayout{// Prepare for other actions
|
||||
ActionButton{
|
||||
Layout.topMargin: 5
|
||||
Layout.leftMargin: 5
|
||||
backgroundRadius: width/2
|
||||
isCustom: true
|
||||
colorSet: SettingsWindowStyle.buttons.back
|
||||
onClicked: logViewer.active = false
|
||||
}
|
||||
ActionButton{
|
||||
Layout.topMargin: 5
|
||||
Layout.leftMargin: 5
|
||||
backgroundRadius: width/2
|
||||
isCustom: true
|
||||
colorSet: SettingsWindowStyle.buttons.copy
|
||||
onClicked: {updating = true ; Clipboard.text = SettingsModel.getLogText();updating=false}
|
||||
}
|
||||
}
|
||||
ListView {
|
||||
id: idContentListView
|
||||
model: logBackground.stringList
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
|
||||
delegate: Text {
|
||||
width: idContentListView.width
|
||||
text: model.modelData
|
||||
font.pointSize: FormTableStyle.entry.text.pointSize
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -54,5 +54,33 @@ QtObject {
|
|||
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
|
||||
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
|
||||
}
|
||||
property QtObject back: QtObject {
|
||||
property int iconSize: 35
|
||||
property string icon : 'back_custom'
|
||||
property string name : 'back'
|
||||
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 's_n_b_bg').color
|
||||
property color backgroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_d', icon, 's_d_b_bg').color
|
||||
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 's_h_b_bg').color
|
||||
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 's_p_b_bg').color
|
||||
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 's_n_b_fg').color
|
||||
property color foregroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_d', icon, 's_d_b_fg').color
|
||||
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 's_h_b_fg').color
|
||||
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 's_p_b_fg').color
|
||||
}
|
||||
property QtObject copy: QtObject {
|
||||
property int iconSize: 30
|
||||
property string icon : 'copy_custom'
|
||||
property string name : 'copy'
|
||||
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'l_n_b_bg').color
|
||||
property color backgroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_d', icon, 'l_d_b_bg').color
|
||||
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'l_h_b_bg').color
|
||||
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'l_p_b_bg').color
|
||||
property color backgroundUpdatingColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_u', icon, 'l_p_b_bg').color
|
||||
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'l_n_b_fg').color
|
||||
property color foregroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_d', icon, 'l_d_b_fg').color
|
||||
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'l_h_b_fg').color
|
||||
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'l_p_b_fg').color
|
||||
property color foregroundUpdatingColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_u', icon, 'l_p_b_fg').color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 9542d335ea525de66d7b5519bac4e1ba808caa3a
|
||||
Subproject commit 1355eaf8e34f3505f3812743bd2232e01558fde2
|
||||
Loading…
Add table
Reference in a new issue